From d94ca10127080b0a9ef9f22a2ba752cd43948c01 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 8 Oct 2026 12:27:15 -0400 Subject: [PATCH 01/25] replace download of pre-built core with apt-get call --- .../perf-eval/backfill-test/run-backfill.sh | 1 + .../backfill-test/runner/instantiate.go | 21 +++++++------------ .../perf-eval/bootstrap-common.sh | 18 +++++++++++++--- .../ingest-load-test/run-load-test.sh | 1 + .../ingest-load-test/runner/instantiate.go | 10 +-------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh index 3e316b6f2..cbc3ef2f9 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh @@ -7,4 +7,5 @@ LEG_TITLE="Backfill ingestion" bootstrap_box +install_core run_leg ./cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go index 4276c2de1..240101210 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go @@ -25,7 +25,7 @@ import ( const ( // runner runs w/ cwd = repo root, so paths are relative to there legDir = "cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test" - corePath = "/usr/local/bin/stellar-core" // fetched from S3 + corePath = "/usr/bin/stellar-core" // apt-installed by install_core // daemon output; the box log's console/syslog tee drains at ~45 KB/s, which throttled request logging daemonLogPath = "/var/log/stellar-rpc.log" ) @@ -76,9 +76,9 @@ func instantiate(ctx context.Context) error { if err != nil { return bail("loading AWS config: %v", err) } - fetch := &harness.S3Fetcher{Client: s3.NewFromConfig(awsCfg), Bucket: cfg.Bucket} + s3Client := s3.NewFromConfig(awsCfg) - coreCfg, err := prepareFixtures(ctx, fetch, repoRoot, cfg.WorkDir, binaryPath) + coreCfg, err := prepareFixtures(ctx, repoRoot, cfg.WorkDir, binaryPath) if err != nil { return bail("%v", err) } @@ -114,7 +114,7 @@ func instantiate(ctx context.Context) error { return bail("writing results: %v", err) } if err := harness.PublishResult( - ctx, fetch.Client, cfg.Bucket, cfg.ResultKey, "ok", cfg.RunID, cfg.TargetSHA, cfg.ResultsFile, ""); err != nil { + ctx, s3Client, cfg.Bucket, cfg.ResultKey, "ok", cfg.RunID, cfg.TargetSHA, cfg.ResultsFile, ""); err != nil { return bail("publishing result: %v", err) } @@ -125,18 +125,11 @@ func instantiate(ctx context.Context) error { return nil } -// prepareFixtures fetches stellar-core, builds stellar-rpc into binaryPath, -// and writes the SDK's captive-core pubnet config, returning its path. +// prepareFixtures builds stellar-rpc into binaryPath and writes the SDK's +// captive-core pubnet config, returning its path. func prepareFixtures( - ctx context.Context, fetch *harness.S3Fetcher, repoRoot, workDir, binaryPath string, + ctx context.Context, repoRoot, workDir, binaryPath string, ) (string, error) { - if err := fetch.FetchVerified(ctx, "core/stellar-core.zst", corePath, true, "stellar-core"); err != nil { - return "", err - } - if err := os.Chmod(corePath, 0o755); err != nil { - return "", fmt.Errorf("chmod stellar-core: %w", err) - } - logger.Infof("building stellar-rpc") if err := harness.RunStreaming(ctx, repoRoot, nil, 40, "make", "build-libs"); err != nil { return "", fmt.Errorf("make build-libs failed: %w", err) diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh index 76a96b95f..ebdcadadd 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh @@ -81,9 +81,7 @@ bootstrap_box() { rm -rf "$WORK_DIR/stellar-rpc" log "installing build deps" - apt-get install -y -qq --no-install-recommends \ - git build-essential \ - libpq5 libsodium23 libunwind8 libc++1-14 + apt-get install -y -qq --no-install-recommends git build-essential GO_VERSION=1.26.5 curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" | tar -xz -C /usr/local @@ -122,6 +120,20 @@ bootstrap_box() { log "checked out $TARGET_SHA; handing off to the Go runner" } +# install_core installs the latest stable stellar-core, so captive core keeps up with pubnet protocol upgrades. +install_core() { + log "installing stellar-core" + local codename + codename=$(. /etc/os-release && echo "$VERSION_CODENAME") + curl -fsSL https://apt.stellar.org/SDF.asc -o /etc/apt/trusted.gpg.d/SDF.asc + # the core package links libc++ 20, which comes from apt.llvm.org + curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc + echo "deb https://apt.stellar.org $codename stable" > /etc/apt/sources.list.d/SDF.list + echo "deb http://apt.llvm.org/$codename/ llvm-toolchain-$codename-20 main" > /etc/apt/sources.list.d/llvm.list + apt-get update -qq -o Acquire::Retries=3 + apt-get install -y -qq --no-install-recommends -o Acquire::Retries=3 stellar-core +} + # run_leg hands off to a leg's runner package. The Go runner owns the # verdict from here, so the bootstrap ERR trap is released first. On a non-zero # exit the runner has written the failure body to RESULTS_FILE (or died before diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh index 7c92d157b..b62b74f1b 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh @@ -7,6 +7,7 @@ log "clearing stale apply-load state" rm -f /tmp/bench-results.json /tmp/load-test-ledgers-*.xdr.zstd bootstrap_box +install_core # The integration test binary links rpcv2's grocksdb + zstd; build them like CI's setup-go does. log "building native libs (zstd, rocksdb)" diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/runner/instantiate.go b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/runner/instantiate.go index c4405d2a8..457309702 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/runner/instantiate.go +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/runner/instantiate.go @@ -92,7 +92,7 @@ func instantiate(ctx context.Context) error { return nil } -// fetchCorpus streams the golden DB, stellar-core, and ledger bundles from S3, +// fetchCorpus streams the golden DB and ledger bundles from S3, // returning the bundle paths and the golden DB fetch duration. func fetchCorpus(ctx context.Context, fetch *harness.S3Fetcher, goldenDB string) ([]string, int, error) { // current/prev1/prev2 lets a run fall back to an older golden DB snapshot @@ -115,14 +115,6 @@ func fetchCorpus(ctx context.Context, fetch *harness.S3Fetcher, goldenDB string) return nil, 0, errors.New("no golden.sqlite.zst in current/, prev1/, or prev2/") } - const corePath = "/usr/local/bin/stellar-core" // fetch pre-built core cached in S3 - if err := fetch.FetchVerified(ctx, "core/stellar-core.zst", corePath, true, "stellar-core"); err != nil { - return nil, 0, err - } - if err := os.Chmod(corePath, 0o755); err != nil { - return nil, 0, fmt.Errorf("chmod stellar-core: %w", err) - } - var bundlePaths []string for _, sc := range ledgerScenarios { bundlePath := fmt.Sprintf("/tmp/load-test-ledgers-%s-%s.xdr.zstd", curVersion, sc) From 4a14ef60cab42bb9348e48be4748569c8b0ab462 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 24 Sep 2026 11:13:21 -0400 Subject: [PATCH 02/25] use stellar-experimental fork of creachadair's jrpc2 --- .../get_ledger_entries_test.go | 2 +- .../integrationtest/transaction_test.go | 2 +- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 6 ++--- cmd/stellar-rpc/internal/jsonrpc/specs.go | 2 +- .../internal/jsonrpc/specs_test.go | 2 +- .../internal/methods/differential_test.go | 2 +- .../internal/methods/get_events.go | 2 +- .../methods/get_events_differential_test.go | 2 +- .../internal/methods/get_fee_stats.go | 2 +- .../internal/methods/get_health.go | 2 +- .../internal/methods/get_latest_ledger.go | 2 +- .../methods/get_latest_ledger_test.go | 2 +- .../internal/methods/get_ledger_entries.go | 2 +- .../internal/methods/get_ledgers.go | 2 +- .../internal/methods/get_network.go | 2 +- .../internal/methods/get_transaction.go | 2 +- .../internal/methods/get_transactions.go | 4 ++-- .../get_transactions_differential_test.go | 2 +- .../internal/methods/get_transactions_test.go | 2 +- .../internal/methods/get_version_info.go | 2 +- cmd/stellar-rpc/internal/methods/handler.go | 4 ++-- .../internal/methods/handler_test.go | 4 ++-- .../internal/methods/send_transaction.go | 2 +- .../internal/methods/simulate_transaction.go | 2 +- .../methods/simulate_transaction_test.go | 2 +- cmd/stellar-rpc/internal/network/backlogQ.go | 2 +- .../internal/network/backlogQ_test.go | 2 +- .../network/requestdurationlimiter.go | 2 +- .../network/requestdurationlimiter_test.go | 6 ++--- .../internal/rpcv2/eventsapi/get_events_v1.go | 2 +- .../internal/rpcv2/eventsapi/query_events.go | 2 +- .../rpcv2/eventsapi/query_events_test.go | 2 +- .../rpcv2/eventsapi/v1_parity_test.go | 4 ++-- .../integrationtest/query_events_test.go | 2 +- cmd/stellar-rpc/internal/rpcv2/jsonrpc.go | 2 +- .../internal/rpcv2/jsonrpc_test.go | 2 +- .../internal/rpcv2/ledger_reads_bench_test.go | 4 ++-- .../internal/rpcv2/rpcv2test/rpcv2test.go | 4 ++-- go.mod | 5 +++-- go.sum | 22 ++++++++++++------- 40 files changed, 65 insertions(+), 58 deletions(-) diff --git a/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go b/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go index da3f20568..6aa6548b6 100644 --- a/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go +++ b/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/integrationtest/transaction_test.go b/cmd/stellar-rpc/internal/integrationtest/transaction_test.go index cb68e517e..dc17e39e5 100644 --- a/cmd/stellar-rpc/internal/integrationtest/transaction_test.go +++ b/cmd/stellar-rpc/internal/integrationtest/transaction_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index 1f691e488..fa4d783e2 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -15,12 +15,12 @@ import ( "time" "unicode" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" - "github.com/creachadair/jrpc2/jhttp" "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus" "github.com/rs/cors" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" + "github.com/stellar-experimental/jrpc2/jhttp" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/jsonrpc/specs.go b/cmd/stellar-rpc/internal/jsonrpc/specs.go index d01b50de2..9083e972d 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/specs.go +++ b/cmd/stellar-rpc/internal/jsonrpc/specs.go @@ -3,7 +3,7 @@ package jsonrpc import ( "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/jsonrpc/specs_test.go b/cmd/stellar-rpc/internal/jsonrpc/specs_test.go index 33457f6c5..bc19bdfb1 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/specs_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/specs_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/differential_test.go b/cmd/stellar-rpc/internal/methods/differential_test.go index a24d6f9f8..198d27fe6 100644 --- a/cmd/stellar-rpc/internal/methods/differential_test.go +++ b/cmd/stellar-rpc/internal/methods/differential_test.go @@ -7,7 +7,7 @@ import ( "slices" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/network" diff --git a/cmd/stellar-rpc/internal/methods/get_events.go b/cmd/stellar-rpc/internal/methods/get_events.go index fa2451b47..1096be484 100644 --- a/cmd/stellar-rpc/internal/methods/get_events.go +++ b/cmd/stellar-rpc/internal/methods/get_events.go @@ -7,8 +7,8 @@ import ( "math" "time" - "github.com/creachadair/jrpc2" "github.com/pkg/errors" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/methods/get_events_differential_test.go b/cmd/stellar-rpc/internal/methods/get_events_differential_test.go index 3bdb780df..a25211026 100644 --- a/cmd/stellar-rpc/internal/methods/get_events_differential_test.go +++ b/cmd/stellar-rpc/internal/methods/get_events_differential_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" "github.com/pkg/errors" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/ingest" diff --git a/cmd/stellar-rpc/internal/methods/get_fee_stats.go b/cmd/stellar-rpc/internal/methods/get_fee_stats.go index 31367ca89..c6956ccda 100644 --- a/cmd/stellar-rpc/internal/methods/get_fee_stats.go +++ b/cmd/stellar-rpc/internal/methods/get_fee_stats.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_health.go b/cmd/stellar-rpc/internal/methods/get_health.go index cb1552333..0e0f9ddbd 100644 --- a/cmd/stellar-rpc/internal/methods/get_health.go +++ b/cmd/stellar-rpc/internal/methods/get_health.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go index f54f47126..8f57c61e5 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "fmt" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/xdr" diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go index caaabfc3f..b52dcccf8 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go @@ -6,7 +6,7 @@ import ( "iter" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/methods/get_ledger_entries.go b/cmd/stellar-rpc/internal/methods/get_ledger_entries.go index 418540cf4..95a6ca4d4 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledger_entries.go +++ b/cmd/stellar-rpc/internal/methods/get_ledger_entries.go @@ -5,7 +5,7 @@ import ( "fmt" "sort" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_ledgers.go b/cmd/stellar-rpc/internal/methods/get_ledgers.go index 961134a09..1afcab4e6 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledgers.go +++ b/cmd/stellar-rpc/internal/methods/get_ledgers.go @@ -7,7 +7,7 @@ import ( "fmt" "strconv" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_network.go b/cmd/stellar-rpc/internal/methods/get_network.go index ef013a286..fb73ef14b 100644 --- a/cmd/stellar-rpc/internal/methods/get_network.go +++ b/cmd/stellar-rpc/internal/methods/get_network.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_transaction.go b/cmd/stellar-rpc/internal/methods/get_transaction.go index e180e7911..a46ca3af3 100644 --- a/cmd/stellar-rpc/internal/methods/get_transaction.go +++ b/cmd/stellar-rpc/internal/methods/get_transaction.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions.go b/cmd/stellar-rpc/internal/methods/get_transactions.go index a81704f05..5cb89fef6 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions.go @@ -6,8 +6,8 @@ import ( "fmt" "strconv" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" "github.com/stellar/go-stellar-sdk/ingest" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go b/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go index 26ed2983c..000ff0488 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go @@ -10,7 +10,7 @@ import ( "strconv" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/ingest" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions_test.go b/cmd/stellar-rpc/internal/methods/get_transactions_test.go index b8b1114f6..499ea3c30 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions_test.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions_test.go @@ -7,7 +7,7 @@ import ( "iter" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/methods/get_version_info.go b/cmd/stellar-rpc/internal/methods/get_version_info.go index 54d7c1266..264ec8af8 100644 --- a/cmd/stellar-rpc/internal/methods/get_version_info.go +++ b/cmd/stellar-rpc/internal/methods/get_version_info.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/handler.go b/cmd/stellar-rpc/internal/methods/handler.go index 2dc4d99bc..145592ff0 100644 --- a/cmd/stellar-rpc/internal/methods/handler.go +++ b/cmd/stellar-rpc/internal/methods/handler.go @@ -1,8 +1,8 @@ package methods import ( - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" ) func NewHandler(fn any) jrpc2.Handler { diff --git a/cmd/stellar-rpc/internal/methods/handler_test.go b/cmd/stellar-rpc/internal/methods/handler_test.go index 147c16c04..b7ee4864e 100644 --- a/cmd/stellar-rpc/internal/methods/handler_test.go +++ b/cmd/stellar-rpc/internal/methods/handler_test.go @@ -4,8 +4,8 @@ import ( "context" "testing" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/methods/send_transaction.go b/cmd/stellar-rpc/internal/methods/send_transaction.go index 2717ffb1e..ed940c904 100644 --- a/cmd/stellar-rpc/internal/methods/send_transaction.go +++ b/cmd/stellar-rpc/internal/methods/send_transaction.go @@ -5,8 +5,8 @@ import ( "encoding/hex" "encoding/json" - "github.com/creachadair/jrpc2" "github.com/pkg/errors" + "github.com/stellar-experimental/jrpc2" "github.com/stellar/go-stellar-sdk/network" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/simulate_transaction.go b/cmd/stellar-rpc/internal/methods/simulate_transaction.go index 0130ddf71..a94da9019 100644 --- a/cmd/stellar-rpc/internal/methods/simulate_transaction.go +++ b/cmd/stellar-rpc/internal/methods/simulate_transaction.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go b/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go index 72c466b3e..79f8c2f23 100644 --- a/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go +++ b/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go @@ -8,7 +8,7 @@ import ( "fmt" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/network/backlogQ.go b/cmd/stellar-rpc/internal/network/backlogQ.go index 15a6e25bc..ae96b9a29 100644 --- a/cmd/stellar-rpc/internal/network/backlogQ.go +++ b/cmd/stellar-rpc/internal/network/backlogQ.go @@ -7,7 +7,7 @@ import ( "net/http" "sync/atomic" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stellar/go-stellar-sdk/support/log" ) diff --git a/cmd/stellar-rpc/internal/network/backlogQ_test.go b/cmd/stellar-rpc/internal/network/backlogQ_test.go index 7f69a06ef..d4b4fa365 100644 --- a/cmd/stellar-rpc/internal/network/backlogQ_test.go +++ b/cmd/stellar-rpc/internal/network/backlogQ_test.go @@ -8,7 +8,7 @@ import ( "sync/atomic" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go index 6cc3ee170..49d4bd633 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go @@ -9,7 +9,7 @@ import ( "runtime" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go index b22970f84..be975cbc1 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go @@ -8,9 +8,9 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" - "github.com/creachadair/jrpc2/jhttp" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" + "github.com/stellar-experimental/jrpc2/jhttp" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go index 84fc71636..431aaa507 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go @@ -13,7 +13,7 @@ import ( "fmt" "math" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go index 84e150ae0..b7a4bfbac 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go index 69a7f1b7b..971f3998c 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go index 6825bf1c8..b60137eac 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go @@ -16,8 +16,8 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go b/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go index f5daf6fe7..b7e03ce15 100644 --- a/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go b/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go index bda16a5b6..8e4be8b1e 100644 --- a/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go +++ b/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go @@ -5,7 +5,7 @@ import ( "math" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" supportlog "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go index 61f08bd1c..446f5c902 100644 --- a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" + "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go b/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go index 8fd4242f4..a6bd643b8 100644 --- a/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/handler" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/network" diff --git a/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go b/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go index d07b9de27..1e304255d 100644 --- a/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go +++ b/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go @@ -15,9 +15,9 @@ import ( "slices" "testing" - "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/server" "github.com/sirupsen/logrus" + "github.com/stellar-experimental/jrpc2" + "github.com/stellar-experimental/jrpc2/server" "github.com/stretchr/testify/require" sdkingest "github.com/stellar/go-stellar-sdk/ingest" diff --git a/go.mod b/go.mod index 356fec125..f0ad85135 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/aws/smithy-go v1.28.1 github.com/caarlos0/env/v11 v11.4.1 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/creachadair/jrpc2 v1.3.3 github.com/dustin/go-humanize v1.0.1 github.com/fsouza/fake-gcs-server v1.49.2 github.com/go-chi/chi v4.1.2+incompatible @@ -32,9 +31,11 @@ require ( ) require ( + github.com/creachadair/jrpc2 v1.2.0 github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 + github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f go.uber.org/goleak v1.3.0 ) @@ -100,7 +101,7 @@ require ( github.com/aws/aws-sdk-go v1.55.8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/creachadair/mds v0.25.10 // indirect + github.com/creachadair/mds v0.31.0 // indirect github.com/djherbis/fscache v0.10.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/go.sum b/go.sum index 217bc880f..a1ef6f721 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -108,10 +108,14 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/jrpc2 v1.2.0 h1:SXr0OgnwM0X18P+HccJP0uT3KGSDk/BCSRlJBvE2bMY= +github.com/creachadair/jrpc2 v1.2.0/go.mod h1:66uKSdr6tR5ZeNvkIjDSbbVUtOv0UhjS/vcd8ECP7Iw= github.com/creachadair/jrpc2 v1.3.3 h1:v+qxzRhHBInD5JFFmCyQ5l0gq60Sneg3zH+QraT+2q8= github.com/creachadair/jrpc2 v1.3.3/go.mod h1:79Ws3bltA8gWyDLVSzKsLnGZJWirKuTCnS7nbewwWzQ= github.com/creachadair/mds v0.25.10 h1:9k9JB35D1xhOCFl0liBhagBBp8fWWkKZrA7UXsfoHtA= github.com/creachadair/mds v0.25.10/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs= +github.com/creachadair/mds v0.31.0 h1:h4SXXgallvZfD8CFQnQNAOtBkUGpZjPwNBRQpHpHM/g= +github.com/creachadair/mds v0.31.0/go.mod h1:IZ3oLffgVNZ4O5WXTPxx7EX5ilSl1ypdv5FekYyynyI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -124,11 +128,11 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= @@ -180,8 +184,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= @@ -252,9 +256,9 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linxGnu/grocksdb v1.10.7 h1:fCi4qvZWo04VgFwGWmO8HQJgUVounJBy+C2TMVPU/ho= github.com/linxGnu/grocksdb v1.10.7/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -267,9 +271,9 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.71 h1:No9XfOKTYi6i0GnBj+WZwD8WP5GZfL7n7GOjRqCdAjA= @@ -349,6 +353,8 @@ github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcG github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d h1:1IK5JNx1uZq/bth4Me2tDGvK6H/cJoAli6DWFcgCo2s= github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d/go.mod h1:bcErD+KvYtyrGqjaSjQeT7raOmlENi0WaORRwIJmfFE= +github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f h1:ploQIKYGC/YOojGZPjhFsT32b4H4ls/TwEoDzNWJFQc= +github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= @@ -358,14 +364,14 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= -github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= From 8832514e90f29c816a61844de8fb058d34275fb9 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 24 Sep 2026 11:13:21 -0400 Subject: [PATCH 03/25] disable jrpc2 built-in concurrency control --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index fa4d783e2..c8125ebed 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -230,6 +230,7 @@ func NewHandler(params Params) Handler { // Disable built-in rpc.* methods (e.g. rpc.serverInfo) that // bypass the handler allowlist and request limiters. DisableBuiltin: true, + Concurrency: -1, // disable built-in jrpc2 concurrency control, use limiters below }, } From 3dd38e5c48d8d512358ab0724949f7b19118b9f8 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Tue, 6 Oct 2026 12:43:28 -0400 Subject: [PATCH 04/25] bump fork version --- go.mod | 4 ++-- go.sum | 24 ++++++++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index f0ad85135..2b20b6aba 100644 --- a/go.mod +++ b/go.mod @@ -31,11 +31,10 @@ require ( ) require ( - github.com/creachadair/jrpc2 v1.2.0 github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 - github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f + github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a go.uber.org/goleak v1.3.0 ) @@ -64,6 +63,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect github.com/bits-and-blooms/bitset v1.24.2 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/creachadair/jrpc2 v1.2.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect diff --git a/go.sum b/go.sum index a1ef6f721..b68dfc73d 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs= cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -110,10 +110,6 @@ github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7X github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/jrpc2 v1.2.0 h1:SXr0OgnwM0X18P+HccJP0uT3KGSDk/BCSRlJBvE2bMY= github.com/creachadair/jrpc2 v1.2.0/go.mod h1:66uKSdr6tR5ZeNvkIjDSbbVUtOv0UhjS/vcd8ECP7Iw= -github.com/creachadair/jrpc2 v1.3.3 h1:v+qxzRhHBInD5JFFmCyQ5l0gq60Sneg3zH+QraT+2q8= -github.com/creachadair/jrpc2 v1.3.3/go.mod h1:79Ws3bltA8gWyDLVSzKsLnGZJWirKuTCnS7nbewwWzQ= -github.com/creachadair/mds v0.25.10 h1:9k9JB35D1xhOCFl0liBhagBBp8fWWkKZrA7UXsfoHtA= -github.com/creachadair/mds v0.25.10/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs= github.com/creachadair/mds v0.31.0 h1:h4SXXgallvZfD8CFQnQNAOtBkUGpZjPwNBRQpHpHM/g= github.com/creachadair/mds v0.31.0/go.mod h1:IZ3oLffgVNZ4O5WXTPxx7EX5ilSl1ypdv5FekYyynyI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -128,11 +124,11 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= @@ -184,8 +180,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= @@ -256,9 +252,9 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linxGnu/grocksdb v1.10.7 h1:fCi4qvZWo04VgFwGWmO8HQJgUVounJBy+C2TMVPU/ho= github.com/linxGnu/grocksdb v1.10.7/go.mod h1:OLQKZwiKwaJiAVCsOzWKvwiLwfZ5Vz8Md5TYR7t7pM8= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -271,9 +267,9 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.71 h1:No9XfOKTYi6i0GnBj+WZwD8WP5GZfL7n7GOjRqCdAjA= @@ -351,10 +347,10 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a h1:HoaJ/8FbwkKJq0o9V+TPe8tPHB2r0Pm2jg5o+PqbI8U= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d h1:1IK5JNx1uZq/bth4Me2tDGvK6H/cJoAli6DWFcgCo2s= github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d/go.mod h1:bcErD+KvYtyrGqjaSjQeT7raOmlENi0WaORRwIJmfFE= -github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f h1:ploQIKYGC/YOojGZPjhFsT32b4H4ls/TwEoDzNWJFQc= -github.com/stellar-experimental/jrpc2 v0.0.0-20260916041201-6eeba1479b1f/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= @@ -364,14 +360,14 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= -github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= From ef80c93ddf05f46f7cb94ab83535ccd762224ad8 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Tue, 6 Oct 2026 13:51:58 -0400 Subject: [PATCH 05/25] bump sdk version to include sdk w/ jrpc2 fork --- go.mod | 4 +--- go.sum | 10 ++-------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 2b20b6aba..296df3a18 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d + github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3 github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) @@ -63,7 +63,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.39.0 // indirect github.com/bits-and-blooms/bitset v1.24.2 // indirect github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect - github.com/creachadair/jrpc2 v1.2.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect @@ -113,7 +112,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect - github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index b68dfc73d..0366775e3 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/jrpc2 v1.2.0 h1:SXr0OgnwM0X18P+HccJP0uT3KGSDk/BCSRlJBvE2bMY= -github.com/creachadair/jrpc2 v1.2.0/go.mod h1:66uKSdr6tR5ZeNvkIjDSbbVUtOv0UhjS/vcd8ECP7Iw= github.com/creachadair/mds v0.31.0 h1:h4SXXgallvZfD8CFQnQNAOtBkUGpZjPwNBRQpHpHM/g= github.com/creachadair/mds v0.31.0/go.mod h1:IZ3oLffgVNZ4O5WXTPxx7EX5ilSl1ypdv5FekYyynyI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -140,8 +138,6 @@ github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -220,8 +216,6 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= @@ -349,8 +343,8 @@ github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcG github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a h1:HoaJ/8FbwkKJq0o9V+TPe8tPHB2r0Pm2jg5o+PqbI8U= github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= -github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d h1:1IK5JNx1uZq/bth4Me2tDGvK6H/cJoAli6DWFcgCo2s= -github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d/go.mod h1:bcErD+KvYtyrGqjaSjQeT7raOmlENi0WaORRwIJmfFE= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3 h1:Y2G/v50+73ueasQaZ0qFlgyZNNOc6q6rl48/P1HSeMw= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3/go.mod h1:k0zj+GDQNA0Ns7+3sYf/1QvR3ZOqWHQhmaKvRAnPz+E= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From c6797637729547c288e857306ce7e159572a2583 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Tue, 6 Oct 2026 15:59:31 -0400 Subject: [PATCH 06/25] bump jrpc2 fork and sdk, disable jrpc2 concurrency control with math.MaxInt --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 3 ++- go.mod | 4 ++-- go.sum | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index c8125ebed..e633682fd 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -9,6 +9,7 @@ package jsonrpc import ( "context" "errors" + "math" "net/http" "strconv" "strings" @@ -230,7 +231,7 @@ func NewHandler(params Params) Handler { // Disable built-in rpc.* methods (e.g. rpc.serverInfo) that // bypass the handler allowlist and request limiters. DisableBuiltin: true, - Concurrency: -1, // disable built-in jrpc2 concurrency control, use limiters below + Concurrency: math.MaxInt, // disable built-in jrpc2 concurrency control, use limiters below }, } diff --git a/go.mod b/go.mod index 296df3a18..018741368 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3 + github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) @@ -34,7 +34,7 @@ require ( github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 - github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a + github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff go.uber.org/goleak v1.3.0 ) diff --git a/go.sum b/go.sum index 0366775e3..cb44ff2b1 100644 --- a/go.sum +++ b/go.sum @@ -341,10 +341,10 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a h1:HoaJ/8FbwkKJq0o9V+TPe8tPHB2r0Pm2jg5o+PqbI8U= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006155236-314cc34fb17a/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3 h1:Y2G/v50+73ueasQaZ0qFlgyZNNOc6q6rl48/P1HSeMw= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006173851-ef998b0d14a3/go.mod h1:k0zj+GDQNA0Ns7+3sYf/1QvR3ZOqWHQhmaKvRAnPz+E= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff h1:5DzfQ0ZpqY7cvSwhculNC0G1mQdiQKqitd6avax5bjA= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f h1:IXcM9NHAAFQe7/VoeXPGLEoDA1Z8gP6RlM94McgI1V8= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f/go.mod h1:ydb3sG69ffnxc3U4FRlM9lN1ORcOgmAZYCro17wGzZQ= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From e7731a364ef8d495f9a39403c4eaad8ae46de48c Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Tue, 6 Oct 2026 17:31:04 -0400 Subject: [PATCH 07/25] drop stale Handler.Close accept guarantee, the fork's bridge holds no POST state Co-Authored-By: Claude Opus 5.5 --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index e633682fd..3bd06edb7 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -58,7 +58,6 @@ type Handler struct { } // Close closes all the resources held by the Handler instances. -// After Close is called the Handler instance will stop accepting JSON RPC requests. func (h Handler) Close() { if err := h.bridge.Close(); err != nil { h.logger.WithError(err).Warn("could not close bridge") From 65d42e183958805830b8a1851258072c3905fbc1 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Tue, 6 Oct 2026 19:10:48 -0400 Subject: [PATCH 08/25] bump jrpc2 fork onto upstream v1.3.5 and sdk --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 018741368..f23189ae5 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f + github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00 github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) @@ -34,7 +34,7 @@ require ( github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 - github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff + github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00 go.uber.org/goleak v1.3.0 ) diff --git a/go.sum b/go.sum index cb44ff2b1..c14ed531b 100644 --- a/go.sum +++ b/go.sum @@ -341,10 +341,10 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff h1:5DzfQ0ZpqY7cvSwhculNC0G1mQdiQKqitd6avax5bjA= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006194504-10b31695dcff/go.mod h1:voblHlxIhDbNDfFFru71nMPJj0UrMaMbtbqx2S2socI= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f h1:IXcM9NHAAFQe7/VoeXPGLEoDA1Z8gP6RlM94McgI1V8= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006195330-7906df02a83f/go.mod h1:ydb3sG69ffnxc3U4FRlM9lN1ORcOgmAZYCro17wGzZQ= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00 h1:GdpEHI0bCduOT+oJa7Uh6zuzCjcDGFdGyJW6D/TzNPY= +github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00 h1:ehoIg1vrEtsY2HeDSahLf2pS7xffK6uP8C0wdzayrSc= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00/go.mod h1:q3FREpX9MUdj9/U76oTcSIPlJ+ZoT0olaGd3OUB5DYI= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From 09a0cc6bb5ebbf2f59d55b1e2f152c4083be5372 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Wed, 7 Oct 2026 15:28:47 -0400 Subject: [PATCH 09/25] add test to ensure parity with old cancellation behavior --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 8 +++ .../internal/jsonrpc/jsonrpc_test.go | 52 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index 3bd06edb7..5cb8365a1 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -219,6 +219,14 @@ func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry) j requestDurationWarnCounter, requestDurationLimitCounter, logger) + if spec.MethodName == protocol.SendTransactionMethodName { + // The bridge cancels a handler's context when its client disconnects. + // Submission has side effects, so let it finish; the duration limit + // still applies. + return func(ctx context.Context, req *jrpc2.Request) (any, error) { + return durationLimiter.Handle(context.WithoutCancel(ctx), req) + } + } return durationLimiter.Handle } diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go new file mode 100644 index 000000000..d65c8ebda --- /dev/null +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go @@ -0,0 +1,52 @@ +package jsonrpc + +import ( + "context" + "testing" + "time" + + "github.com/stellar-experimental/jrpc2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" + "github.com/stellar/go-stellar-sdk/support/log" + + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/host" +) + +// A client disconnect cancels the handler context, except for sendTransaction, +// which keeps its duration limit. +func TestWrapWithLimiters_ClientDisconnect(t *testing.T) { + for _, test := range []struct { + method string + wantCanceled bool + }{ + {protocol.SendTransactionMethodName, false}, + {protocol.GetHealthMethodName, true}, + } { + t.Run(test.method, func(t *testing.T) { + ctx, disconnect := context.WithCancel(t.Context()) + var deadline bool + h := wrapWithLimiters(HandlerSpec{ + MethodName: test.method, + Handler: func(hctx context.Context, _ *jrpc2.Request) (any, error) { + disconnect() + _, deadline = hctx.Deadline() + return nil, hctx.Err() + }, + QueueLimit: 1, + RequestDurationLimit: time.Minute, + }, host.MakeNoOpDaemon(), log.DefaultLogger) + + req := (&jrpc2.ParsedRequest{ID: "1", Method: test.method}).ToRequest() + _, err := h(ctx, req) + if test.wantCanceled { + require.ErrorIs(t, err, context.Canceled) + } else { + require.NoError(t, err) + } + assert.True(t, deadline, "duration limit not applied") + }) + } +} From 0db854dce5d49e17dbd0a9529aec9fc5aab615fb Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Wed, 7 Oct 2026 15:47:38 -0400 Subject: [PATCH 10/25] bump import version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f23189ae5..33f226b0c 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 - github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00 + github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 go.uber.org/goleak v1.3.0 ) diff --git a/go.sum b/go.sum index c14ed531b..7e1cfb761 100644 --- a/go.sum +++ b/go.sum @@ -341,8 +341,8 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00 h1:GdpEHI0bCduOT+oJa7Uh6zuzCjcDGFdGyJW6D/TzNPY= -github.com/stellar-experimental/jrpc2 v0.0.0-20261006225839-5b4c6315ee00/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= +github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 h1:It0yf2sJmm+FmSCqZqBege4RG1HVGpUQEkIb/EA3HvI= +github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00 h1:ehoIg1vrEtsY2HeDSahLf2pS7xffK6uP8C0wdzayrSc= github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00/go.mod h1:q3FREpX9MUdj9/U76oTcSIPlJ+ZoT0olaGd3OUB5DYI= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= From acad4555bb8c3956f930df07fd3ed15df736278d Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Wed, 7 Oct 2026 15:52:05 -0400 Subject: [PATCH 11/25] cleanup go.mod leftovers --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 33f226b0c..06d71d319 100644 --- a/go.mod +++ b/go.mod @@ -100,7 +100,7 @@ require ( github.com/aws/aws-sdk-go v1.55.8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/creachadair/mds v0.31.0 // indirect + github.com/creachadair/mds v0.26.1 // indirect github.com/djherbis/fscache v0.10.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/go.sum b/go.sum index 7e1cfb761..78cafe627 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/mds v0.31.0 h1:h4SXXgallvZfD8CFQnQNAOtBkUGpZjPwNBRQpHpHM/g= -github.com/creachadair/mds v0.31.0/go.mod h1:IZ3oLffgVNZ4O5WXTPxx7EX5ilSl1ypdv5FekYyynyI= +github.com/creachadair/mds v0.26.1 h1:CQG8f4cueHX/c20q5Sy/Ubk8Bvy+aRzVgbpxVieMBAs= +github.com/creachadair/mds v0.26.1/go.mod h1:dMBTCSy3iS3dwh4Rb1zxeZz2d7K8+N24GCTsayWtQRI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= From a6205a37b0662afe2a57530063ee214af1c168a4 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Wed, 7 Oct 2026 15:53:34 -0400 Subject: [PATCH 12/25] bump SDK to version using jrpc2 fork --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06d71d319..9f4ffd0ce 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00 + github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) diff --git a/go.sum b/go.sum index 78cafe627..c501e4255 100644 --- a/go.sum +++ b/go.sum @@ -343,8 +343,8 @@ github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcG github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 h1:It0yf2sJmm+FmSCqZqBege4RG1HVGpUQEkIb/EA3HvI= github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00 h1:ehoIg1vrEtsY2HeDSahLf2pS7xffK6uP8C0wdzayrSc= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261006230825-3e6c13bb6b00/go.mod h1:q3FREpX9MUdj9/U76oTcSIPlJ+ZoT0olaGd3OUB5DYI= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d h1:AvmTS5jDMmlDHp8Eikw8Mgi50Ra/T/stG0Z5P3Jodj4= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d/go.mod h1:0XH6kYNZbMRX8eUOvzgGbiRFrxyOh3tOJMjZXnUuCTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From b99e8ebf4c711cab1ddec8783fbc2131fc3fd2dc Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Wed, 7 Oct 2026 16:26:29 -0400 Subject: [PATCH 13/25] launch coordinator run for verification; bump SDK to latest commit --- .github/workflows/load-test-coordinator.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/load-test-coordinator.yml b/.github/workflows/load-test-coordinator.yml index 6342b221a..c191242ea 100644 --- a/.github/workflows/load-test-coordinator.yml +++ b/.github/workflows/load-test-coordinator.yml @@ -11,7 +11,7 @@ defaults: on: push: - branches: [release/**] + branches: [release/**, bypass-jrpc2-marshal] workflow_dispatch: inputs: target_ref: diff --git a/go.mod b/go.mod index 9f4ffd0ce..59dcb6bfb 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d + github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) diff --git a/go.sum b/go.sum index c501e4255..eb28f020b 100644 --- a/go.sum +++ b/go.sum @@ -343,8 +343,8 @@ github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcG github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 h1:It0yf2sJmm+FmSCqZqBege4RG1HVGpUQEkIb/EA3HvI= github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d h1:AvmTS5jDMmlDHp8Eikw8Mgi50Ra/T/stG0Z5P3Jodj4= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261007194816-71239c5f814d/go.mod h1:0XH6kYNZbMRX8eUOvzgGbiRFrxyOh3tOJMjZXnUuCTs= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b h1:+tSsejBZgb+v+Pb0PYveOXFHnTxzaI0mOniyMrW8np4= +github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b/go.mod h1:0XH6kYNZbMRX8eUOvzgGbiRFrxyOh3tOJMjZXnUuCTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From 98cb49a9fc633c5621f9198887b1da1a546b2241 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 8 Oct 2026 14:58:28 -0400 Subject: [PATCH 14/25] restore jrpc2's default concurrency cap --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index 5cb8365a1..6d67aec83 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -9,7 +9,6 @@ package jsonrpc import ( "context" "errors" - "math" "net/http" "strconv" "strings" @@ -238,7 +237,6 @@ func NewHandler(params Params) Handler { // Disable built-in rpc.* methods (e.g. rpc.serverInfo) that // bypass the handler allowlist and request limiters. DisableBuiltin: true, - Concurrency: math.MaxInt, // disable built-in jrpc2 concurrency control, use limiters below }, } From 82394804985565627e195e6b9684da442631d78c Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 8 Oct 2026 15:44:27 -0400 Subject: [PATCH 15/25] report requests that fail after their context ends as canceled, not errors --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 7 ++++++- cmd/stellar-rpc/internal/methods/get_ledgers.go | 8 ++++++-- cmd/stellar-rpc/internal/methods/get_version_info.go | 2 +- cmd/stellar-rpc/internal/preflight/preflight.go | 4 +++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index 6d67aec83..a9cc08f28 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -119,7 +119,8 @@ func decorateHandlers(daemon host.Daemon, logger *log.Entry, m handler.Map) hand duration := time.Since(startTime) label := prometheus.Labels{"endpoint": r.Method(), "status": "ok"} simulateTransactionResponse, ok := result.(protocol.SimulateTransactionResponse) - if ok && simulateTransactionResponse.Error != "" { + simulateFailed := ok && simulateTransactionResponse.Error != "" + if simulateFailed { label[labelStatus] = "error" } else if err != nil { var jsonRPCErr *jrpc2.Error @@ -129,6 +130,10 @@ func decorateHandlers(daemon host.Daemon, logger *log.Entry, m handler.Map) hand label[labelStatus] = status } } + if ctx.Err() != nil && (err != nil || simulateFailed) { + // Failed after its context ended: the client left or timed out, not a server fault. + label[labelStatus] = "canceled" + } requestMetric.With(label).Observe(duration.Seconds()) logResponse(logger, reqID, duration, label[labelStatus]) return result, err diff --git a/cmd/stellar-rpc/internal/methods/get_ledgers.go b/cmd/stellar-rpc/internal/methods/get_ledgers.go index 1afcab4e6..03b904970 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledgers.go +++ b/cmd/stellar-rpc/internal/methods/get_ledgers.go @@ -40,6 +40,8 @@ func NewGetLedgersHandler(ledgerReader store.LedgerReader, maxLimit, defaultLimi // getLedgers fetch ledgers and relevant metadata from DB and falling back to // the remote rpcdatastore if necessary. +// +//nolint:cyclop // the context check on the datastore log adds one branch func (h ledgersHandler) getLedgers( ctx context.Context, request protocol.GetLedgersRequest, ) (protocol.GetLedgersResponse, error) { @@ -70,8 +72,10 @@ func (h ledgersHandler) getLedgers( if h.datastoreLedgerReader != nil { dsRange, err := h.datastoreLedgerReader.GetAvailableLedgerRange(ctx) if err != nil { - // log error but continue using local ledger range - h.logger.WithError(err).Error("failed to get available ledger range from datastore") + // log error but continue using local ledger range; a canceled request is not a fault + if ctx.Err() == nil { + h.logger.WithError(err).Error("failed to get available ledger range from datastore") + } } else { // extend available range to include datastore availableLedgerRange.FirstLedger = min(dsRange.FirstLedger, availableLedgerRange.FirstLedger) diff --git a/cmd/stellar-rpc/internal/methods/get_version_info.go b/cmd/stellar-rpc/internal/methods/get_version_info.go index 264ec8af8..e44ce30a9 100644 --- a/cmd/stellar-rpc/internal/methods/get_version_info.go +++ b/cmd/stellar-rpc/internal/methods/get_version_info.go @@ -24,7 +24,7 @@ func NewGetVersionInfoHandler( // version once it starts core, which is after handlers are built. captiveCoreVersion := daemon.CoreVersion() protocolVersion, err := getProtocolVersion(ctx, ledgerReader) - if err != nil { + if err != nil && ctx.Err() == nil { // a canceled request is not a fault logger.WithError(err).Error("failed to fetch protocol version") } diff --git a/cmd/stellar-rpc/internal/preflight/preflight.go b/cmd/stellar-rpc/internal/preflight/preflight.go index 6f808e21f..c977a84f5 100644 --- a/cmd/stellar-rpc/internal/preflight/preflight.go +++ b/cmd/stellar-rpc/internal/preflight/preflight.go @@ -56,7 +56,9 @@ func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey C.xdr_t) C.ledger_entry_an } entries, _, err := h.ledgerEntryGetter.GetLedgerEntries(h.ctx, []xdr.LedgerKey{ledgerKey}) if err != nil { - h.logger.WithError(err).Error("SnapshotSourceGet(): GetLedgerEntries() failed") + if h.ctx.Err() == nil { // a canceled request is not a fault + h.logger.WithError(err).Error("SnapshotSourceGet(): GetLedgerEntries() failed") + } return C.ledger_entry_and_ttl_t{} } if len(entries) > 1 { From 882337b79c25cdbd1b2cdbd04473df7d9b780c6b Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 8 Oct 2026 17:22:35 -0400 Subject: [PATCH 16/25] serve JSON-RPC over HTTP in-tree on upstream jrpc2, replace the fork's bridge --- .../get_ledger_entries_test.go | 2 +- .../integrationtest/transaction_test.go | 2 +- cmd/stellar-rpc/internal/jsonrpc/bridge.go | 262 ++++++++++++++++++ .../internal/jsonrpc/bridge_test.go | 210 ++++++++++++++ cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 35 +-- .../internal/jsonrpc/jsonrpc_test.go | 2 +- cmd/stellar-rpc/internal/jsonrpc/specs.go | 2 +- .../internal/jsonrpc/specs_test.go | 2 +- .../internal/methods/differential_test.go | 2 +- .../internal/methods/get_events.go | 2 +- .../methods/get_events_differential_test.go | 2 +- .../internal/methods/get_fee_stats.go | 2 +- .../internal/methods/get_health.go | 2 +- .../internal/methods/get_latest_ledger.go | 2 +- .../methods/get_latest_ledger_test.go | 2 +- .../internal/methods/get_ledger_entries.go | 2 +- .../internal/methods/get_ledgers.go | 2 +- .../internal/methods/get_network.go | 2 +- .../internal/methods/get_transaction.go | 2 +- .../internal/methods/get_transactions.go | 4 +- .../get_transactions_differential_test.go | 2 +- .../internal/methods/get_transactions_test.go | 2 +- .../internal/methods/get_version_info.go | 2 +- cmd/stellar-rpc/internal/methods/handler.go | 4 +- .../internal/methods/handler_test.go | 4 +- .../internal/methods/send_transaction.go | 2 +- .../internal/methods/simulate_transaction.go | 2 +- .../methods/simulate_transaction_test.go | 2 +- cmd/stellar-rpc/internal/network/backlogQ.go | 2 +- .../internal/network/backlogQ_test.go | 2 +- .../network/requestdurationlimiter.go | 2 +- .../network/requestdurationlimiter_test.go | 6 +- .../internal/rpcv2/eventsapi/get_events_v1.go | 2 +- .../internal/rpcv2/eventsapi/query_events.go | 2 +- .../rpcv2/eventsapi/query_events_test.go | 2 +- .../rpcv2/eventsapi/v1_parity_test.go | 4 +- .../integrationtest/query_events_test.go | 2 +- cmd/stellar-rpc/internal/rpcv2/jsonrpc.go | 2 +- .../internal/rpcv2/jsonrpc_test.go | 2 +- .../internal/rpcv2/ledger_reads_bench_test.go | 4 +- .../internal/rpcv2/rpcv2test/rpcv2test.go | 4 +- go.mod | 7 +- go.sum | 16 +- 43 files changed, 541 insertions(+), 81 deletions(-) create mode 100644 cmd/stellar-rpc/internal/jsonrpc/bridge.go create mode 100644 cmd/stellar-rpc/internal/jsonrpc/bridge_test.go diff --git a/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go b/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go index 6aa6548b6..da3f20568 100644 --- a/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go +++ b/cmd/stellar-rpc/internal/integrationtest/get_ledger_entries_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/integrationtest/transaction_test.go b/cmd/stellar-rpc/internal/integrationtest/transaction_test.go index dc17e39e5..cb68e517e 100644 --- a/cmd/stellar-rpc/internal/integrationtest/transaction_test.go +++ b/cmd/stellar-rpc/internal/integrationtest/transaction_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge.go b/cmd/stellar-rpc/internal/jsonrpc/bridge.go new file mode 100644 index 000000000..49d23b9b6 --- /dev/null +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge.go @@ -0,0 +1,262 @@ +package jsonrpc + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "net" + "net/http" + "runtime" + "slices" + "strconv" + "strings" + "sync" + + "github.com/creachadair/jrpc2" + "golang.org/x/sync/semaphore" +) + +const contentTypeJSON = "application/json" + +var ( + errBridgeClosed = errors.New("the bridge is closed") + errEmptyMethod = &jrpc2.Error{Code: jrpc2.InvalidRequest, Message: "empty method name"} + errNoSuchMethod = &jrpc2.Error{Code: jrpc2.MethodNotFound, Message: jrpc2.MethodNotFound.String()} +) + +// bridge serves JSON-RPC 2.0 over HTTP POST by calling the handlers of an +// Assigner directly, so a result is encoded once and a json.RawMessage result +// is written as is. It stands in for jhttp.Bridge, which ran every request +// through an in-memory client and server and re-encoded each response on the +// way, and keeps its wire behavior; TestBridge_ParityWithJHTTP checks that. +type bridge struct { + methods jrpc2.Assigner + sem *semaphore.Weighted // bounds the handlers running at once, as jrpc2.Server does + + mu sync.RWMutex // read-held while a request's handlers run + closed bool + ctx context.Context //nolint:containedctx // ends at Close, canceling the requests in flight + cancel context.CancelFunc +} + +func newBridge(methods jrpc2.Assigner) *bridge { + ctx, cancel := context.WithCancel(context.Background()) + return &bridge{ + methods: methods, + sem: semaphore.NewWeighted(int64(runtime.NumCPU())), + ctx: ctx, + cancel: cancel, + } +} + +// Close cancels the requests in flight, waits for their handlers to return, +// and fails later requests. +func (b *bridge) Close() { + b.cancel() + b.mu.Lock() + b.closed = true + b.mu.Unlock() +} + +// ServeHTTP implements http.Handler. +func (b *bridge) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Accept-Post", contentTypeJSON) + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + mt, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) + if mt != contentTypeJSON { + http.Error(w, "content-type must be application/json", http.StatusUnsupportedMediaType) + return + } + if cs, ok := params["charset"]; ok && cs != "utf-8" && cs != "utf8" { + http.Error(w, "invalid content-type charset", http.StatusUnsupportedMediaType) + return + } + if err := b.serve(w, r); err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, err.Error()) //nolint:gosec // a read or parse error, as text; jhttp.Bridge did the same + } +} + +// serve answers the requests in r's body. +func (b *bridge) serve(w http.ResponseWriter, r *http.Request) error { + body, err := io.ReadAll(r.Body) + if err != nil { + return err + } + reqs, err := jrpc2.ParseRequests(body) + if err != nil { + return err + } + batch := len(reqs) > 0 && reqs[0].Batch + // Invalid requests are answered first, as jhttp.Bridge did. + slices.SortStableFunc(reqs, func(x, y *jrpc2.ParsedRequest) int { return rank(x) - rank(y) }) + rsps, err := b.run(r.Context(), reqs) + if err != nil { + return err + } + if len(rsps) == 0 { + w.WriteHeader(http.StatusNoContent) // only notifications, or an empty batch + return nil + } + return write(w, batch, rsps) +} + +// rank orders invalid requests before valid ones. +func rank(pr *jrpc2.ParsedRequest) int { + if pr.Error != nil { + return 0 + } + return 1 +} + +// run calls the handlers for reqs, on a context that also ends at Close, and +// returns the responses in request order. Notifications get none. +func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*response, error) { + b.mu.RLock() + defer b.mu.RUnlock() + if b.closed { + return nil, errBridgeClosed + } + ctx, cancel := context.WithCancel(ctx) + defer cancel() + defer context.AfterFunc(b.ctx, cancel)() //nolint:contextcheck // b.ctx ends at Close, not with the request + + rsps := make([]*response, len(reqs)) + var wg sync.WaitGroup + for i, pr := range reqs { + method, rsp := b.route(ctx, pr) + if method == nil { + rsps[i] = rsp + continue + } + if err := b.sem.Acquire(ctx, 1); err != nil { + rsps[i] = respond(pr, nil, err) + continue + } + call := func() { + defer b.sem.Release(1) + v, err := method(ctx, pr.ToRequest()) + rsps[i] = respond(pr, v, err) + } + if i == len(reqs)-1 { + call() // the last request runs here, as in jrpc2.Server + } else { + wg.Go(call) + } + } + wg.Wait() + return slices.DeleteFunc(rsps, func(r *response) bool { return r == nil }), nil +} + +// route returns pr's handler, or else the response pr gets without one: +// invalid requests are always answered, notifications to unknown methods never. +func (b *bridge) route(ctx context.Context, pr *jrpc2.ParsedRequest) (jrpc2.Handler, *response) { + if pr.Error != nil { + return nil, &response{id: pr.ID, err: pr.Error} + } + if pr.Method != "" { + if method := b.methods.Assign(ctx, pr.Method); method != nil { + return method, nil + } + } + switch { + case pr.ID == "": + return nil, nil + case pr.Method == "": + return nil, &response{id: pr.ID, err: errEmptyMethod} + } + return nil, &response{id: pr.ID, err: errNoSuchMethod.WithData(pr.Method)} +} + +// respond builds the response to pr from a handler's result, passing a +// non-empty json.RawMessage through as is. A notification gets none. +func respond(pr *jrpc2.ParsedRequest, v any, err error) *response { + if pr.ID == "" { + return nil + } + rsp := &response{id: pr.ID} + if err == nil { + raw, ok := v.(json.RawMessage) + if !ok || len(raw) == 0 { + raw, err = json.Marshal(v) + } + if err == nil { + rsp.result = raw + return rsp + } + } + if e, ok := err.(*jrpc2.Error); ok { //nolint:errorlint // as jrpc2.Server does: no unwrapping + rsp.err = e + } else if c := jrpc2.ErrorCode(err); c != jrpc2.NoError { + rsp.err = &jrpc2.Error{Code: c, Message: err.Error()} + } else { + rsp.err = &jrpc2.Error{Code: jrpc2.InternalError, Message: err.Error()} + } + return rsp +} + +// A response is one JSON-RPC response, with a result or an error. +type response struct { + id string // the request ID as sent, "" for null + result json.RawMessage + err *jrpc2.Error +} + +// write writes rsps as the body of a 200 response, as an array if batch is +// set. Results are written without copying. +func write(w http.ResponseWriter, batch bool, rsps []*response) error { + parts := make(net.Buffers, 0, 3*len(rsps)+2) + if batch { + parts = append(parts, []byte("[")) + } + for i, r := range rsps { + head := `{"jsonrpc":"2.0","id":` + escapeID(r.id) + if i > 0 { + head = "," + head + } + body := r.result + if r.err != nil { + e, err := json.Marshal(r.err) + if err != nil { + return err + } + head, body = head+`,"error":`, e + } else { + head += `,"result":` + } + parts = append(parts, []byte(head), body, []byte("}")) + } + if batch { + parts = append(parts, []byte("]")) + } + n := 0 + for _, p := range parts { + n += len(p) + } + w.Header().Set("Content-Type", contentTypeJSON) + w.Header().Set("Content-Length", strconv.Itoa(n)) + w.WriteHeader(http.StatusOK) + _, _ = parts.WriteTo(w) // a write error means the client is gone + return nil +} + +// escapeID HTML-escapes a request ID before it is echoed, as re-encoding the +// response through jhttp.Bridge did. An absent ID is null. +func escapeID(id string) string { + if id == "" { + return "null" + } + if !strings.ContainsAny(id, "<>&\u2028\u2029") { + return id + } + var buf bytes.Buffer + json.HTMLEscape(&buf, []byte(id)) + return buf.String() +} diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go new file mode 100644 index 000000000..62a412a82 --- /dev/null +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go @@ -0,0 +1,210 @@ +package jsonrpc + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + "time" + + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" + "github.com/creachadair/jrpc2/jhttp" + "github.com/stretchr/testify/require" +) + +// bridgeMethods is the method table the bridge tests serve. Its raw result is +// compact and free of HTML characters, so jhttp.Bridge re-encodes it to the +// bytes the bridge passes through. +func bridgeMethods() handler.Map { + return handler.Map{ + "echo": handler.New(func(_ context.Context, r *jrpc2.Request) (any, error) { + var v any + if err := r.UnmarshalParams(&v); err != nil { + return nil, err + } + return v, nil + }), + "html": handler.New(func(context.Context) (any, error) { + return map[string]string{"s": "&"}, nil + }), + "raw": handler.New(func(context.Context) (any, error) { + return json.RawMessage(`{"pre":"rendered"}`), nil + }), + "rawEmpty": handler.New(func(context.Context) (any, error) { + return json.RawMessage(nil), nil + }), + "ptrErr": handler.New(func(context.Context) (any, error) { + return nil, &jrpc2.Error{Code: jrpc2.InvalidParams, Message: "", Data: json.RawMessage(`{"k":1}`)} + }), + "valErr": handler.New(func(context.Context) (any, error) { + return nil, jrpc2.Error{Code: jrpc2.InternalError, Message: "val"} + }), + "plainErr": handler.New(func(context.Context) (any, error) { + return nil, errors.New("boom") + }), + "canceled": handler.New(func(context.Context) (any, error) { + return nil, context.Canceled + }), + "unmarshalable": handler.New(func(context.Context) (any, error) { + return make(chan int), nil + }), + } +} + +type bridgeCase struct { + name, method, contentType, body string +} + +func bridgeCases() []bridgeCase { + call := func(name, body string) bridgeCase { + return bridgeCase{name: name, method: http.MethodPost, contentType: contentTypeJSON, body: body} + } + const echo = `{"jsonrpc":"2.0","id":1,"method":"echo","params":[1]}` + return []bridgeCase{ + call("call", `{"jsonrpc":"2.0","id":1,"method":"echo","params":[1,"a",{"b":null}]}`), + call("null result", `{"jsonrpc":"2.0","id":1,"method":"echo"}`), + call("html in result", `{"jsonrpc":"2.0","id":1,"method":"html"}`), + call("raw result", `{"jsonrpc":"2.0","id":"r","method":"raw"}`), + call("empty raw result", `{"jsonrpc":"2.0","id":"r","method":"rawEmpty"}`), + call("pointer error", `{"jsonrpc":"2.0","id":1,"method":"ptrErr"}`), + call("value error", `{"jsonrpc":"2.0","id":1,"method":"valErr"}`), + call("plain error", `{"jsonrpc":"2.0","id":1,"method":"plainErr"}`), + call("canceled error", `{"jsonrpc":"2.0","id":1,"method":"canceled"}`), + call("unmarshalable result", `{"jsonrpc":"2.0","id":1,"method":"unmarshalable"}`), + call("notification", `{"jsonrpc":"2.0","method":"echo","params":[1]}`), + call("notification to unknown method", `{"jsonrpc":"2.0","method":"nope"}`), + call("notification with empty method", `{"jsonrpc":"2.0","method":""}`), + call("notification with bad params", `{"jsonrpc":"2.0","method":"echo","params":"s"}`), + call("unknown method", `{"jsonrpc":"2.0","id":1,"method":"nope"}`), + call("empty method", `{"jsonrpc":"2.0","id":1,"method":""}`), + call("bad params", `{"jsonrpc":"2.0","id":1,"method":"echo","params":"s"}`), + call("bad version", `{"jsonrpc":"1.0","id":1,"method":"echo"}`), + call("html id", `{"jsonrpc":"2.0","id":"&`+"\u2028"+`","method":"echo","params":[]}`), + call("html id on invalid request", `{"jsonrpc":"2.0","id":"&","method":"echo","params":"s"}`), + call("null id", `{"jsonrpc":"2.0","id":null,"method":"echo","params":[]}`), + call("number id", `{"jsonrpc":"2.0","id":1.5e3,"method":"echo","params":[]}`), + call("batch", `[`+echo+`,`+ + `{"jsonrpc":"2.0","method":"echo","params":[2]},`+ + `{"jsonrpc":"2.0","id":2,"method":"nope"},`+ + `{"jsonrpc":"1.0","id":3,"method":"echo"},`+ + `{"jsonrpc":"2.0","id":"","method":"ptrErr"},`+ + `{"jsonrpc":"2.0","id":4,"method":"valErr"},`+ + `{"jsonrpc":"2.0","id":5,"method":"raw"}]`), + call("batch of one", `[`+echo+`]`), + call("batch of notifications", `[{"jsonrpc":"2.0","method":"echo"},{"jsonrpc":"2.0","method":"nope"}]`), + call("batch with invalid entries", `[1,`+echo+`,"x"]`), + call("empty batch", `[]`), + call("empty object", `{}`), + call("invalid json", `{"jsonrpc":`), + call("empty body", ``), + call("not json", `hello`), + {name: "get", method: http.MethodGet, contentType: contentTypeJSON, body: ``}, + {name: "put", method: http.MethodPut, contentType: contentTypeJSON, body: echo}, + {name: "text content type", method: http.MethodPost, contentType: "text/plain", body: echo}, + {name: "no content type", method: http.MethodPost, contentType: "", body: echo}, + {name: "json-rpc content type", method: http.MethodPost, contentType: "application/json-rpc", body: echo}, + {name: "charset utf-8", method: http.MethodPost, contentType: contentTypeJSON + "; charset=utf-8", body: echo}, + {name: "charset UTF8", method: http.MethodPost, contentType: contentTypeJSON + "; charset=UTF8", body: echo}, + {name: "charset latin1", method: http.MethodPost, contentType: contentTypeJSON + "; charset=latin1", body: echo}, + } +} + +func serveBridge(t *testing.T, h http.Handler, tc bridgeCase) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequestWithContext(t.Context(), tc.method, "/", strings.NewReader(tc.body)) + if tc.contentType != "" { + req.Header.Set("Content-Type", tc.contentType) + } + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + return rec +} + +func postBridge(t *testing.T, h http.Handler, body string) *httptest.ResponseRecorder { + t.Helper() + return serveBridge(t, h, bridgeCase{method: http.MethodPost, contentType: contentTypeJSON, body: body}) +} + +// The bridge answers every request with the status, headers and body that +// jhttp.Bridge, the transport it replaces, gives on upstream jrpc2. +func TestBridge_ParityWithJHTTP(t *testing.T) { + methods := bridgeMethods() + old := jhttp.NewBridge(methods, &jhttp.BridgeOptions{Server: &jrpc2.ServerOptions{DisableBuiltin: true}}) + t.Cleanup(func() { _ = old.Close() }) + b := newBridge(methods) + t.Cleanup(b.Close) + for _, tc := range bridgeCases() { + t.Run(tc.name, func(t *testing.T) { + want, got := serveBridge(t, old, tc), serveBridge(t, b, tc) + require.Equal(t, want.Code, got.Code) + require.Equal(t, want.Header(), got.Header()) + require.Equal(t, want.Body.String(), got.Body.String()) + }) + } +} + +// A non-empty json.RawMessage result is written as is, with the Content-Length +// to match. +func TestBridge_RawResultPassthrough(t *testing.T) { + raw := json.RawMessage("{\n \"pretty\": \"\"\n}") + b := newBridge(handler.Map{"raw": handler.New(func(context.Context) (any, error) { return raw, nil })}) + t.Cleanup(b.Close) + rec := postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"raw"}`) + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, `{"jsonrpc":"2.0","id":1,"result":`+string(raw)+`}`, rec.Body.String()) + require.Equal(t, strconv.Itoa(rec.Body.Len()), rec.Header().Get("Content-Length")) +} + +// A handler runs on the HTTP request's context, so a client disconnect +// cancels it, and its error reaches the wire as a cancellation. +func TestBridge_RequestContext(t *testing.T) { + ctx, disconnect := context.WithCancel(t.Context()) + b := newBridge(handler.Map{"m": handler.New(func(hctx context.Context) (any, error) { + disconnect() + return nil, hctx.Err() + })}) + t.Cleanup(b.Close) + req := httptest.NewRequestWithContext(ctx, http.MethodPost, "/", + strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"m"}`)) + req.Header.Set("Content-Type", contentTypeJSON) + rec := httptest.NewRecorder() + b.ServeHTTP(rec, req) + require.Equal(t, http.StatusOK, rec.Code) + require.JSONEq(t, + fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"error":{"code":%d,"message":"context canceled"}}`, + jrpc2.ErrorCode(context.Canceled)), + rec.Body.String()) +} + +// Close cancels the requests in flight, waits for their handlers to return, +// and fails the requests after it, as jhttp.Bridge.Close did. +func TestBridge_CloseDrains(t *testing.T) { + started, unwound := make(chan struct{}), make(chan struct{}) + b := newBridge(handler.Map{"slow": handler.New(func(hctx context.Context) (any, error) { + close(started) + <-hctx.Done() + time.Sleep(50 * time.Millisecond) // unwinding takes a while + close(unwound) + return nil, hctx.Err() + })}) + served := make(chan *httptest.ResponseRecorder, 1) + go func() { served <- postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"slow"}`) }() + <-started + b.Close() + select { + case <-unwound: + default: + t.Fatal("Close returned before the handler did") + } + require.Equal(t, http.StatusOK, (<-served).Code) + + rec := postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"slow"}`) + require.Equal(t, http.StatusInternalServerError, rec.Code) + require.Equal(t, errBridgeClosed.Error()+"\n", rec.Body.String()) +} diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index a9cc08f28..3c2eb165f 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -15,12 +15,11 @@ import ( "time" "unicode" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus" "github.com/rs/cors" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" - "github.com/stellar-experimental/jrpc2/jhttp" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" @@ -52,15 +51,13 @@ const ( type Handler struct { http.Handler - bridge jhttp.Bridge - logger *log.Entry + bridge *bridge } -// Close closes all the resources held by the Handler instances. +// Close stops accepting JSON-RPC requests, cancels those in flight and waits +// for their handlers to return, so none outlives the stores closed after it. func (h Handler) Close() { - if err := h.bridge.Close(); err != nil { - h.logger.WithError(err).Warn("could not close bridge") - } + h.bridge.Close() } // HandlerSpec describes one JSON-RPC method: its handler plus the per-method @@ -236,24 +233,11 @@ func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry) j // NewHandler constructs a Handler instance from the given method specs func NewHandler(params Params) Handler { - bridgeOptions := jhttp.BridgeOptions{ - Server: &jrpc2.ServerOptions{ - Logger: func(text string) { params.Logger.Debug(text) }, - // Disable built-in rpc.* methods (e.g. rpc.serverInfo) that - // bypass the handler allowlist and request limiters. - DisableBuiltin: true, - }, - } - handlersMap := handler.Map{} for _, spec := range params.Specs { handlersMap[spec.MethodName] = wrapWithLimiters(spec, params.Daemon, params.Logger) } - bridge := jhttp.NewBridge(decorateHandlers( - params.Daemon, - params.Logger, - handlersMap), - &bridgeOptions) + rpc := newBridge(decorateHandlers(params.Daemon, params.Logger, handlersMap)) // globalQueueRequestBacklogLimiter is a metric for measuring the total concurrent inflight requests globalQueueRequestBacklogLimiter := prometheus.NewGauge(prometheus.GaugeOpts{ @@ -262,7 +246,7 @@ func NewHandler(params Params) Handler { }) queueLimitedBridge := network.MakeHTTPBacklogQueueLimiter( - bridge, + rpc, globalQueueRequestBacklogLimiter, uint64(params.GlobalQueueLimit), params.Logger) @@ -297,8 +281,7 @@ func NewHandler(params Params) Handler { }) return Handler{ - bridge: bridge, - logger: params.Logger, + bridge: rpc, Handler: corsMiddleware.Handler(handler), } } diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go index d65c8ebda..c46c3701f 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/jsonrpc/specs.go b/cmd/stellar-rpc/internal/jsonrpc/specs.go index 9083e972d..d01b50de2 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/specs.go +++ b/cmd/stellar-rpc/internal/jsonrpc/specs.go @@ -3,7 +3,7 @@ package jsonrpc import ( "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/jsonrpc/specs_test.go b/cmd/stellar-rpc/internal/jsonrpc/specs_test.go index bc19bdfb1..33457f6c5 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/specs_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/specs_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/differential_test.go b/cmd/stellar-rpc/internal/methods/differential_test.go index 198d27fe6..a24d6f9f8 100644 --- a/cmd/stellar-rpc/internal/methods/differential_test.go +++ b/cmd/stellar-rpc/internal/methods/differential_test.go @@ -7,7 +7,7 @@ import ( "slices" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/network" diff --git a/cmd/stellar-rpc/internal/methods/get_events.go b/cmd/stellar-rpc/internal/methods/get_events.go index 1096be484..fa2451b47 100644 --- a/cmd/stellar-rpc/internal/methods/get_events.go +++ b/cmd/stellar-rpc/internal/methods/get_events.go @@ -7,8 +7,8 @@ import ( "math" "time" + "github.com/creachadair/jrpc2" "github.com/pkg/errors" - "github.com/stellar-experimental/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/methods/get_events_differential_test.go b/cmd/stellar-rpc/internal/methods/get_events_differential_test.go index a25211026..3bdb780df 100644 --- a/cmd/stellar-rpc/internal/methods/get_events_differential_test.go +++ b/cmd/stellar-rpc/internal/methods/get_events_differential_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" + "github.com/creachadair/jrpc2" "github.com/pkg/errors" - "github.com/stellar-experimental/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/ingest" diff --git a/cmd/stellar-rpc/internal/methods/get_fee_stats.go b/cmd/stellar-rpc/internal/methods/get_fee_stats.go index c6956ccda..31367ca89 100644 --- a/cmd/stellar-rpc/internal/methods/get_fee_stats.go +++ b/cmd/stellar-rpc/internal/methods/get_fee_stats.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_health.go b/cmd/stellar-rpc/internal/methods/get_health.go index 0e0f9ddbd..cb1552333 100644 --- a/cmd/stellar-rpc/internal/methods/get_health.go +++ b/cmd/stellar-rpc/internal/methods/get_health.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go index 8f57c61e5..f54f47126 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger.go @@ -6,7 +6,7 @@ import ( "encoding/hex" "fmt" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/xdr" diff --git a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go index b52dcccf8..caaabfc3f 100644 --- a/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go +++ b/cmd/stellar-rpc/internal/methods/get_latest_ledger_test.go @@ -6,7 +6,7 @@ import ( "iter" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/methods/get_ledger_entries.go b/cmd/stellar-rpc/internal/methods/get_ledger_entries.go index 95a6ca4d4..418540cf4 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledger_entries.go +++ b/cmd/stellar-rpc/internal/methods/get_ledger_entries.go @@ -5,7 +5,7 @@ import ( "fmt" "sort" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_ledgers.go b/cmd/stellar-rpc/internal/methods/get_ledgers.go index 03b904970..babe9d3d4 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledgers.go +++ b/cmd/stellar-rpc/internal/methods/get_ledgers.go @@ -7,7 +7,7 @@ import ( "fmt" "strconv" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_network.go b/cmd/stellar-rpc/internal/methods/get_network.go index fb73ef14b..ef013a286 100644 --- a/cmd/stellar-rpc/internal/methods/get_network.go +++ b/cmd/stellar-rpc/internal/methods/get_network.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_transaction.go b/cmd/stellar-rpc/internal/methods/get_transaction.go index a46ca3af3..e180e7911 100644 --- a/cmd/stellar-rpc/internal/methods/get_transaction.go +++ b/cmd/stellar-rpc/internal/methods/get_transaction.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions.go b/cmd/stellar-rpc/internal/methods/get_transactions.go index 5cb89fef6..a81704f05 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions.go @@ -6,8 +6,8 @@ import ( "fmt" "strconv" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" "github.com/stellar/go-stellar-sdk/ingest" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go b/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go index 000ff0488..26ed2983c 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions_differential_test.go @@ -10,7 +10,7 @@ import ( "strconv" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/ingest" diff --git a/cmd/stellar-rpc/internal/methods/get_transactions_test.go b/cmd/stellar-rpc/internal/methods/get_transactions_test.go index 499ea3c30..b8b1114f6 100644 --- a/cmd/stellar-rpc/internal/methods/get_transactions_test.go +++ b/cmd/stellar-rpc/internal/methods/get_transactions_test.go @@ -7,7 +7,7 @@ import ( "iter" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/methods/get_version_info.go b/cmd/stellar-rpc/internal/methods/get_version_info.go index e44ce30a9..027941683 100644 --- a/cmd/stellar-rpc/internal/methods/get_version_info.go +++ b/cmd/stellar-rpc/internal/methods/get_version_info.go @@ -3,7 +3,7 @@ package methods import ( "context" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/handler.go b/cmd/stellar-rpc/internal/methods/handler.go index 145592ff0..2dc4d99bc 100644 --- a/cmd/stellar-rpc/internal/methods/handler.go +++ b/cmd/stellar-rpc/internal/methods/handler.go @@ -1,8 +1,8 @@ package methods import ( - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" ) func NewHandler(fn any) jrpc2.Handler { diff --git a/cmd/stellar-rpc/internal/methods/handler_test.go b/cmd/stellar-rpc/internal/methods/handler_test.go index b7ee4864e..147c16c04 100644 --- a/cmd/stellar-rpc/internal/methods/handler_test.go +++ b/cmd/stellar-rpc/internal/methods/handler_test.go @@ -4,8 +4,8 @@ import ( "context" "testing" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/methods/send_transaction.go b/cmd/stellar-rpc/internal/methods/send_transaction.go index ed940c904..2717ffb1e 100644 --- a/cmd/stellar-rpc/internal/methods/send_transaction.go +++ b/cmd/stellar-rpc/internal/methods/send_transaction.go @@ -5,8 +5,8 @@ import ( "encoding/hex" "encoding/json" + "github.com/creachadair/jrpc2" "github.com/pkg/errors" - "github.com/stellar-experimental/jrpc2" "github.com/stellar/go-stellar-sdk/network" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" diff --git a/cmd/stellar-rpc/internal/methods/simulate_transaction.go b/cmd/stellar-rpc/internal/methods/simulate_transaction.go index a94da9019..0130ddf71 100644 --- a/cmd/stellar-rpc/internal/methods/simulate_transaction.go +++ b/cmd/stellar-rpc/internal/methods/simulate_transaction.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go b/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go index 79f8c2f23..72c466b3e 100644 --- a/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go +++ b/cmd/stellar-rpc/internal/methods/simulate_transaction_test.go @@ -8,7 +8,7 @@ import ( "fmt" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/network/backlogQ.go b/cmd/stellar-rpc/internal/network/backlogQ.go index ae96b9a29..15a6e25bc 100644 --- a/cmd/stellar-rpc/internal/network/backlogQ.go +++ b/cmd/stellar-rpc/internal/network/backlogQ.go @@ -7,7 +7,7 @@ import ( "net/http" "sync/atomic" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stellar/go-stellar-sdk/support/log" ) diff --git a/cmd/stellar-rpc/internal/network/backlogQ_test.go b/cmd/stellar-rpc/internal/network/backlogQ_test.go index d4b4fa365..7f69a06ef 100644 --- a/cmd/stellar-rpc/internal/network/backlogQ_test.go +++ b/cmd/stellar-rpc/internal/network/backlogQ_test.go @@ -8,7 +8,7 @@ import ( "sync/atomic" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go index 49d4bd633..6cc3ee170 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go @@ -9,7 +9,7 @@ import ( "runtime" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go index be975cbc1..b22970f84 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go @@ -8,9 +8,9 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" - "github.com/stellar-experimental/jrpc2/jhttp" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" + "github.com/creachadair/jrpc2/jhttp" "github.com/stretchr/testify/require" ) diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go index 431aaa507..84fc71636 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v1.go @@ -13,7 +13,7 @@ import ( "fmt" "math" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go index b7a4bfbac..84e150ae0 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" "github.com/stellar/go-stellar-sdk/strkey" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go index 971f3998c..69a7f1b7b 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/query_events_test.go @@ -9,7 +9,7 @@ import ( "fmt" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go index b60137eac..6825bf1c8 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/v1_parity_test.go @@ -16,8 +16,8 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go b/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go index b7e03ce15..f5daf6fe7 100644 --- a/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/integrationtest/query_events_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go b/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go index 8e4be8b1e..bda16a5b6 100644 --- a/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go +++ b/cmd/stellar-rpc/internal/rpcv2/jsonrpc.go @@ -5,7 +5,7 @@ import ( "math" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" supportlog "github.com/stellar/go-stellar-sdk/support/log" diff --git a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go index 446f5c902..61f08bd1c 100644 --- a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" + "github.com/creachadair/jrpc2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go b/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go index a6bd643b8..8fd4242f4 100644 --- a/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/ledger_reads_bench_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/handler" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" "github.com/stretchr/testify/require" "github.com/stellar/go-stellar-sdk/network" diff --git a/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go b/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go index 1e304255d..d07b9de27 100644 --- a/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go +++ b/cmd/stellar-rpc/internal/rpcv2/rpcv2test/rpcv2test.go @@ -15,9 +15,9 @@ import ( "slices" "testing" + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/server" "github.com/sirupsen/logrus" - "github.com/stellar-experimental/jrpc2" - "github.com/stellar-experimental/jrpc2/server" "github.com/stretchr/testify/require" sdkingest "github.com/stellar/go-stellar-sdk/ingest" diff --git a/go.mod b/go.mod index 59dcb6bfb..356fec125 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/aws/smithy-go v1.28.1 github.com/caarlos0/env/v11 v11.4.1 github.com/cenkalti/backoff/v4 v4.3.0 + github.com/creachadair/jrpc2 v1.3.3 github.com/dustin/go-humanize v1.0.1 github.com/fsouza/fake-gcs-server v1.49.2 github.com/go-chi/chi v4.1.2+incompatible @@ -25,7 +26,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b + github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 github.com/stretchr/testify v1.12.1 ) @@ -34,7 +35,6 @@ require ( github.com/dchest/siphash v1.2.3 github.com/mattn/go-sqlite3 v1.14.17 github.com/rubenv/sql-migrate v1.5.2 - github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 go.uber.org/goleak v1.3.0 ) @@ -100,7 +100,7 @@ require ( github.com/aws/aws-sdk-go v1.55.8 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/creachadair/mds v0.26.1 // indirect + github.com/creachadair/mds v0.25.10 // indirect github.com/djherbis/fscache v0.10.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -112,6 +112,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index eb28f020b..217bc880f 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,10 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/mds v0.26.1 h1:CQG8f4cueHX/c20q5Sy/Ubk8Bvy+aRzVgbpxVieMBAs= -github.com/creachadair/mds v0.26.1/go.mod h1:dMBTCSy3iS3dwh4Rb1zxeZz2d7K8+N24GCTsayWtQRI= +github.com/creachadair/jrpc2 v1.3.3 h1:v+qxzRhHBInD5JFFmCyQ5l0gq60Sneg3zH+QraT+2q8= +github.com/creachadair/jrpc2 v1.3.3/go.mod h1:79Ws3bltA8gWyDLVSzKsLnGZJWirKuTCnS7nbewwWzQ= +github.com/creachadair/mds v0.25.10 h1:9k9JB35D1xhOCFl0liBhagBBp8fWWkKZrA7UXsfoHtA= +github.com/creachadair/mds v0.25.10/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -138,6 +140,8 @@ github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -216,6 +220,8 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= @@ -341,10 +347,8 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= -github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56 h1:It0yf2sJmm+FmSCqZqBege4RG1HVGpUQEkIb/EA3HvI= -github.com/stellar-experimental/jrpc2 v0.0.0-20261007183142-4ada2aea5d56/go.mod h1:hjol63R4QOhng0oOk7D60LxXTQoKNaXMl8MFLZ2hjVk= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b h1:+tSsejBZgb+v+Pb0PYveOXFHnTxzaI0mOniyMrW8np4= -github.com/stellar/go-stellar-sdk v0.7.4-0.20261007200820-dfb8bbf4347b/go.mod h1:0XH6kYNZbMRX8eUOvzgGbiRFrxyOh3tOJMjZXnUuCTs= +github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d h1:1IK5JNx1uZq/bth4Me2tDGvK6H/cJoAli6DWFcgCo2s= +github.com/stellar/go-stellar-sdk v0.7.4-0.20260927163220-0f59d2edfb0d/go.mod h1:bcErD+KvYtyrGqjaSjQeT7raOmlENi0WaORRwIJmfFE= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a h1:40YIhQusSioBKDW5Tr+YdjoXphcVeu7eXUS2ibMQBTs= github.com/stellar/go-xdr v0.0.0-20260806060815-dc590f17552a/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/streamhash v0.1.1-0.20261008060912-872a4a3b5ea4 h1:X0bHDVDCZB3ZAD94vUffAibUu6G96fALoDCC1KKlMZk= From 4b86a3ce2dd9d2e21e74f0b10ec7f3e1412c23d2 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Thu, 8 Oct 2026 17:52:26 -0400 Subject: [PATCH 17/25] stop copying response into a buffer + enforce global request-duration limit in the bridge --- cmd/stellar-rpc/internal/jsonrpc/bridge.go | 110 ++++++++++-- .../internal/jsonrpc/bridge_test.go | 98 ++++++++++- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 39 ++-- .../network/requestdurationlimiter.go | 166 ------------------ .../network/requestdurationlimiter_test.go | 158 ----------------- 5 files changed, 210 insertions(+), 361 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge.go b/cmd/stellar-rpc/internal/jsonrpc/bridge.go index 49d23b9b6..300609043 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/bridge.go +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge.go @@ -15,9 +15,16 @@ import ( "strconv" "strings" "sync" + "sync/atomic" + "time" "github.com/creachadair/jrpc2" "golang.org/x/sync/semaphore" + + "github.com/stellar/go-stellar-sdk/support/log" + + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/network" + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/util" ) const contentTypeJSON = "application/json" @@ -33,9 +40,12 @@ var ( // is written as is. It stands in for jhttp.Bridge, which ran every request // through an in-memory client and server and re-encoded each response on the // way, and keeps its wire behavior; TestBridge_ParityWithJHTTP checks that. +// It enforces the global duration limit itself, so a response is never +// buffered to be swapped for a 504. type bridge struct { methods jrpc2.Assigner sem *semaphore.Weighted // bounds the handlers running at once, as jrpc2.Server does + limits durationLimits mu sync.RWMutex // read-held while a request's handlers run closed bool @@ -43,11 +53,20 @@ type bridge struct { cancel context.CancelFunc } -func newBridge(methods jrpc2.Assigner) *bridge { +// durationLimits are the global request-duration thresholds, with the counters +// and logger for requests that pass them, as network's HTTP limiter had them. +type durationLimits struct { + warning, limit time.Duration + warnings, timeouts interface{ Inc() } + logger *log.Entry +} + +func newBridge(methods jrpc2.Assigner, limits durationLimits) *bridge { ctx, cancel := context.WithCancel(context.Background()) return &bridge{ methods: methods, sem: semaphore.NewWeighted(int64(runtime.NumCPU())), + limits: limits, ctx: ctx, cancel: cancel, } @@ -64,6 +83,7 @@ func (b *bridge) Close() { // ServeHTTP implements http.Handler. func (b *bridge) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer b.recoverPanic(w) w.Header().Set("Accept-Post", contentTypeJSON) if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) @@ -78,34 +98,88 @@ func (b *bridge) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "invalid content-type charset", http.StatusUnsupportedMediaType) return } - if err := b.serve(w, r); err != nil { + start := time.Now() + ctx, cancel := b.limits.bound(r.Context()) + defer cancel() + batch, rsps, err := b.serve(ctx, r) + if b.limits.timedOut(r, time.Since(start)) { + if r.Context().Err() == nil { + w.WriteHeader(http.StatusGatewayTimeout) + } + return + } + if err == nil && len(rsps) == 0 { + w.WriteHeader(http.StatusNoContent) // only notifications, or an empty batch + return + } + if err == nil { + err = write(w, batch, rsps) + } + if err != nil { w.WriteHeader(http.StatusInternalServerError) _, _ = fmt.Fprintln(w, err.Error()) //nolint:gosec // a read or parse error, as text; jhttp.Bridge did the same } } -// serve answers the requests in r's body. -func (b *bridge) serve(w http.ResponseWriter, r *http.Request) error { +// serve runs the requests in r's body on ctx and returns their responses. +func (b *bridge) serve(ctx context.Context, r *http.Request) (bool, []*response, error) { body, err := io.ReadAll(r.Body) if err != nil { - return err + return false, nil, err } reqs, err := jrpc2.ParseRequests(body) if err != nil { - return err + return false, nil, err } batch := len(reqs) > 0 && reqs[0].Batch // Invalid requests are answered first, as jhttp.Bridge did. slices.SortStableFunc(reqs, func(x, y *jrpc2.ParsedRequest) int { return rank(x) - rank(y) }) - rsps, err := b.run(r.Context(), reqs) - if err != nil { - return err + rsps, err := b.run(ctx, reqs) + return batch, rsps, err +} + +// bound returns ctx ending at the limit, as the handlers see it. +func (l durationLimits) bound(ctx context.Context) (context.Context, context.CancelFunc) { + if l.limit == network.RequestDurationLimiterNoLimit { + return ctx, func() {} } - if len(rsps) == 0 { - w.WriteHeader(http.StatusNoContent) // only notifications, or an empty batch - return nil + return context.WithTimeout(ctx, l.limit) +} + +// timedOut reports whether a request that took elapsed passed the limit, and +// counts and logs it if it passed the limit or, failing that, the warning. +func (l durationLimits) timedOut(r *http.Request, elapsed time.Duration) bool { + switch { + case l.limit == network.RequestDurationLimiterNoLimit: + case l.limit != 0 && elapsed >= l.limit: + l.timeouts.Inc() + l.logger.Infof("Request processing for %s exceed limiting threshold of %v", r.URL.Path, l.limit) + return true + case l.warning != 0 && l.warning < l.limit && elapsed >= l.warning: + l.warnings.Inc() + l.logger.Infof("Request processing for %s exceed warning threshold of %v", r.URL.Path, l.warning) + } + return false +} + +// handlerPanic is the call stack of a panic recovered in a handler's call. +type handlerPanic []string + +// recoverPanic answers 500 to a request whose serving panicked and logs the +// stack, as network's HTTP limiter did. +func (b *bridge) recoverPanic(w http.ResponseWriter) { + p := recover() + if p == nil { + return + } + stack, ok := p.(handlerPanic) + if !ok { + stack = util.CallStack(p, "", "(*bridge).ServeHTTP", 8) + } + w.WriteHeader(http.StatusInternalServerError) + for _, line := range stack { + b.limits.logger.Warn(line) } - return write(w, batch, rsps) } // rank orders invalid requests before valid ones. @@ -130,6 +204,7 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo rsps := make([]*response, len(reqs)) var wg sync.WaitGroup + var crashed atomic.Pointer[handlerPanic] for i, pr := range reqs { method, rsp := b.route(ctx, pr) if method == nil { @@ -142,6 +217,12 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo } call := func() { defer b.sem.Release(1) + defer func() { + if p := recover(); p != nil { + stack := handlerPanic(util.CallStack(p, pr.Method, "(*bridge).run.func", 8)) + crashed.CompareAndSwap(nil, &stack) + } + }() v, err := method(ctx, pr.ToRequest()) rsps[i] = respond(pr, v, err) } @@ -152,6 +233,9 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo } } wg.Wait() + if stack := crashed.Load(); stack != nil { + panic(*stack) // recoverPanic answers 500 for the whole request + } return slices.DeleteFunc(rsps, func(r *response) bool { return r == nil }), nil } diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go index 62a412a82..df3b18644 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go @@ -5,19 +5,44 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "net/http/httptest" "strconv" "strings" + "sync/atomic" "testing" "time" "github.com/creachadair/jrpc2" "github.com/creachadair/jrpc2/handler" "github.com/creachadair/jrpc2/jhttp" + "github.com/sirupsen/logrus" + logtest "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/require" + + "github.com/stellar/go-stellar-sdk/support/log" + + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/network" ) +// unlimited lets requests run as long as their handlers take. +var unlimited = durationLimits{limit: network.RequestDurationLimiterNoLimit, logger: log.DefaultLogger} + +type counter struct{ n atomic.Int64 } + +func (c *counter) Inc() { c.n.Add(1) } + +// testLogger returns a quiet logger whose entries hook records. +func testLogger() (*log.Entry, *logtest.Hook) { + hook := new(logtest.Hook) + logger := log.New() + logger.SetOutput(io.Discard) + logger.SetLevel(logrus.DebugLevel) + logger.AddHook(hook) + return logger, hook +} + // bridgeMethods is the method table the bridge tests serve. Its raw result is // compact and free of HTML characters, so jhttp.Bridge re-encodes it to the // bytes the bridge passes through. @@ -137,7 +162,7 @@ func TestBridge_ParityWithJHTTP(t *testing.T) { methods := bridgeMethods() old := jhttp.NewBridge(methods, &jhttp.BridgeOptions{Server: &jrpc2.ServerOptions{DisableBuiltin: true}}) t.Cleanup(func() { _ = old.Close() }) - b := newBridge(methods) + b := newBridge(methods, unlimited) t.Cleanup(b.Close) for _, tc := range bridgeCases() { t.Run(tc.name, func(t *testing.T) { @@ -153,7 +178,7 @@ func TestBridge_ParityWithJHTTP(t *testing.T) { // to match. func TestBridge_RawResultPassthrough(t *testing.T) { raw := json.RawMessage("{\n \"pretty\": \"\"\n}") - b := newBridge(handler.Map{"raw": handler.New(func(context.Context) (any, error) { return raw, nil })}) + b := newBridge(handler.Map{"raw": handler.New(func(context.Context) (any, error) { return raw, nil })}, unlimited) t.Cleanup(b.Close) rec := postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"raw"}`) require.Equal(t, http.StatusOK, rec.Code) @@ -168,7 +193,7 @@ func TestBridge_RequestContext(t *testing.T) { b := newBridge(handler.Map{"m": handler.New(func(hctx context.Context) (any, error) { disconnect() return nil, hctx.Err() - })}) + })}, unlimited) t.Cleanup(b.Close) req := httptest.NewRequestWithContext(ctx, http.MethodPost, "/", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"m"}`)) @@ -192,7 +217,7 @@ func TestBridge_CloseDrains(t *testing.T) { time.Sleep(50 * time.Millisecond) // unwinding takes a while close(unwound) return nil, hctx.Err() - })}) + })}, unlimited) served := make(chan *httptest.ResponseRecorder, 1) go func() { served <- postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"slow"}`) }() <-started @@ -208,3 +233,68 @@ func TestBridge_CloseDrains(t *testing.T) { require.Equal(t, http.StatusInternalServerError, rec.Code) require.Equal(t, errBridgeClosed.Error()+"\n", rec.Body.String()) } + +// A request that outlasts the global limit is answered 504 with no body, and +// one past the limit or the warning is counted and logged. +func TestBridge_DurationLimits(t *testing.T) { + const ok = `{"jsonrpc":"2.0","id":1,"result":1}` + for _, tc := range []struct { + name string + warning, limit, run time.Duration + wantCode int + wantBody string + wantWarnings, wantLimits int64 + }{ + {"limited", time.Second / 20, time.Second / 10, 10 * time.Second, http.StatusGatewayTimeout, "", 0, 1}, + {"not limited", 5 * time.Second, 10 * time.Second, time.Second / 10, http.StatusOK, ok, 0, 0}, + {"warned", time.Second / 10, 10 * time.Second, time.Second / 5, http.StatusOK, ok, 1, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + var warnings, limits counter + logger, hook := testLogger() + b := newBridge(handler.Map{"slow": handler.New(func(ctx context.Context) (any, error) { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(tc.run): + return 1, nil + } + })}, durationLimits{tc.warning, tc.limit, &warnings, &limits, logger}) + t.Cleanup(b.Close) + rec := postBridge(t, b, `{"jsonrpc":"2.0","id":1,"method":"slow"}`) + require.Equal(t, tc.wantCode, rec.Code) + require.Equal(t, tc.wantBody, rec.Body.String()) + require.Equal(t, tc.wantWarnings, warnings.n.Load()) + require.Equal(t, tc.wantLimits, limits.n.Load()) + require.Len(t, hook.AllEntries(), int(tc.wantWarnings+tc.wantLimits)) + }) + } +} + +// A panic while serving is answered 500 and its stack logged, whether the +// handler ran on the request's goroutine or on its own. +func TestBridge_Panic(t *testing.T) { + methods := handler.Map{ + "panic": handler.New(func(context.Context) (any, error) { panic("test panic") }), + "ok": handler.New(func(context.Context) (any, error) { return 1, nil }), + } + for name, body := range map[string]string{ + "single": `{"jsonrpc":"2.0","id":1,"method":"panic"}`, + "batch element": `[{"jsonrpc":"2.0","id":1,"method":"panic"},{"jsonrpc":"2.0","id":2,"method":"ok"}]`, + } { + t.Run(name, func(t *testing.T) { + logger, hook := testLogger() + b := newBridge(methods, durationLimits{limit: network.RequestDurationLimiterNoLimit, logger: logger}) + t.Cleanup(b.Close) + rec := postBridge(t, b, body) + require.Equal(t, http.StatusInternalServerError, rec.Code) + require.Empty(t, rec.Body.String()) + entries := hook.AllEntries() + require.NotEmpty(t, entries) + require.Equal(t, "test panic when calling panic", entries[0].Message) + for _, e := range entries { + require.Equal(t, logrus.WarnLevel, e.Level) + } + }) + } +} diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index 3c2eb165f..e3438eff2 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -237,19 +237,6 @@ func NewHandler(params Params) Handler { for _, spec := range params.Specs { handlersMap[spec.MethodName] = wrapWithLimiters(spec, params.Daemon, params.Logger) } - rpc := newBridge(decorateHandlers(params.Daemon, params.Logger, handlersMap)) - - // globalQueueRequestBacklogLimiter is a metric for measuring the total concurrent inflight requests - globalQueueRequestBacklogLimiter := prometheus.NewGauge(prometheus.GaugeOpts{ - Namespace: params.Daemon.MetricsNamespace(), Subsystem: subsystemNetwork, Name: "global_inflight_requests", - Help: "Number of concurrenty in-flight http requests", - }) - - queueLimitedBridge := network.MakeHTTPBacklogQueueLimiter( - rpc, - globalQueueRequestBacklogLimiter, - uint64(params.GlobalQueueLimit), - params.Logger) globalQueueRequestExecutionDurationWarningCounter := prometheus.NewCounter(prometheus.CounterOpts{ Namespace: params.Daemon.MetricsNamespace(), @@ -263,15 +250,27 @@ func NewHandler(params Params) Handler { Name: "global_request_execution_duration_threshold_limit", Help: "The metric measures the count of requests that surpassed the limit threshold for execution time", }) - handler := network.MakeHTTPRequestDurationLimiter( - queueLimitedBridge, - params.GlobalDurationWarning, - params.GlobalDurationLimit, - globalQueueRequestExecutionDurationWarningCounter, - globalQueueRequestExecutionDurationLimitCounter, + rpc := newBridge(decorateHandlers(params.Daemon, params.Logger, handlersMap), durationLimits{ + warning: params.GlobalDurationWarning, + limit: params.GlobalDurationLimit, + warnings: globalQueueRequestExecutionDurationWarningCounter, + timeouts: globalQueueRequestExecutionDurationLimitCounter, + logger: params.Logger, + }) + + // globalQueueRequestBacklogLimiter is a metric for measuring the total concurrent inflight requests + globalQueueRequestBacklogLimiter := prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: params.Daemon.MetricsNamespace(), Subsystem: subsystemNetwork, Name: "global_inflight_requests", + Help: "Number of concurrenty in-flight http requests", + }) + + queueLimitedBridge := network.MakeHTTPBacklogQueueLimiter( + rpc, + globalQueueRequestBacklogLimiter, + uint64(params.GlobalQueueLimit), params.Logger) - handler = http.MaxBytesHandler(handler, maxHTTPRequestSize) + handler := http.MaxBytesHandler(queueLimitedBridge, maxHTTPRequestSize) corsMiddleware := cors.New(cors.Options{ AllowedOrigins: []string{}, diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go index 6cc3ee170..6546221b8 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go @@ -2,18 +2,12 @@ package network import ( "context" - "maps" "math" - "net/http" - "reflect" - "runtime" "time" "github.com/creachadair/jrpc2" "github.com/stellar/go-stellar-sdk/support/log" - - "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/util" ) const maxDuration = time.Duration(math.MaxInt64) @@ -37,166 +31,6 @@ type requestDurationLimiter struct { limitCounter increasingCounter } -type httpRequestDurationLimiter struct { - requestDurationLimiter - - httpDownstreamHandler http.Handler -} - -func MakeHTTPRequestDurationLimiter( - downstream http.Handler, - warningThreshold time.Duration, - limitThreshold time.Duration, - warningCounter increasingCounter, - limitCounter increasingCounter, - logger *log.Entry, -) http.Handler { - // make sure the warning threshold is less then the limit threshold; otherwise, just set it to the limit threshold. - if warningThreshold > limitThreshold { - warningThreshold = limitThreshold - } - return &httpRequestDurationLimiter{ - httpDownstreamHandler: downstream, - requestDurationLimiter: requestDurationLimiter{ - warningThreshold: warningThreshold, - limitThreshold: limitThreshold, - logger: logger, - warningCounter: warningCounter, - limitCounter: limitCounter, - }, - } -} - -type bufferedResponseWriter struct { - header http.Header - buffer []byte - statusCode int -} - -func makeBufferedResponseWriter(rw http.ResponseWriter) *bufferedResponseWriter { - header := rw.Header() - bw := &bufferedResponseWriter{ - header: make(http.Header, 0), - } - maps.Copy(bw.header, header) - return bw -} - -func (w *bufferedResponseWriter) Header() http.Header { - return w.header -} - -func (w *bufferedResponseWriter) Write(buf []byte) (int, error) { - w.buffer = append(w.buffer, buf...) - return len(buf), nil -} - -func (w *bufferedResponseWriter) WriteHeader(statusCode int) { - w.statusCode = statusCode -} - -func (w *bufferedResponseWriter) WriteOut(ctx context.Context, rw http.ResponseWriter) { - // update the headers map. - headers := rw.Header() - for k := range headers { - delete(headers, k) - } - maps.Copy(headers, w.header) - - if len(w.buffer) == 0 { - if w.statusCode != 0 { - rw.WriteHeader(w.statusCode) - } - return - } - if w.statusCode != 0 { - rw.WriteHeader(w.statusCode) - } - - if ctx.Err() == nil { - // the following return size/error won't help us much at this point. The request is already finalized. - rw.Write(w.buffer) //nolint:errcheck - } -} - -// TODO: refactor and simplify this function -// -//nolint:gocognit,cyclop -func (q *httpRequestDurationLimiter) ServeHTTP(res http.ResponseWriter, req *http.Request) { - if q.limitThreshold == RequestDurationLimiterNoLimit { - // if specified max duration, pass-through - q.httpDownstreamHandler.ServeHTTP(res, req) - return - } - var warningCh <-chan time.Time - if q.warningThreshold != time.Duration(0) && q.warningThreshold < q.limitThreshold { - warningCh = time.NewTimer(q.warningThreshold).C - } - var limitCh <-chan time.Time - if q.limitThreshold != time.Duration(0) { - limitCh = time.NewTimer(q.limitThreshold).C - } - requestCompleted := make(chan []string, 1) - requestCtx, requestCtxCancel := context.WithTimeout(req.Context(), q.limitThreshold) - defer requestCtxCancel() - timeLimitedRequest := req.WithContext(requestCtx) - responseBuffer := makeBufferedResponseWriter(res) - go func() { - defer func() { - if err := recover(); err != nil { - functionName := runtime.FuncForPC(reflect.ValueOf(q.httpDownstreamHandler.ServeHTTP).Pointer()).Name() - callStack := util.CallStack(err, functionName, "(*httpRequestDurationLimiter).ServeHTTP.func1()", 8) - requestCompleted <- callStack - } else { - close(requestCompleted) - } - }() - q.httpDownstreamHandler.ServeHTTP(responseBuffer, timeLimitedRequest) - }() - - warn := false - for { - select { - case <-warningCh: - // warn - warn = true - case <-limitCh: - // limit - requestCtxCancel() - if q.limitCounter != nil { - q.limitCounter.Inc() - } - if q.logger != nil { - q.logger.Infof("Request processing for %s exceed limiting threshold of %v", req.URL.Path, q.limitThreshold) - } - if req.Context().Err() == nil { - res.WriteHeader(http.StatusGatewayTimeout) - } - return - case errStrings := <-requestCompleted: - if warn { - if q.warningCounter != nil { - q.warningCounter.Inc() - } - if q.logger != nil { - q.logger.Infof("Request processing for %s exceed warning threshold of %v", req.URL.Path, q.warningThreshold) - } - } - if len(errStrings) == 0 { - responseBuffer.WriteOut(req.Context(), res) - } else { - res.WriteHeader(http.StatusInternalServerError) - for _, errStr := range errStrings { - if q.logger != nil { - q.logger.Warn(errStr) - } - } - } - return - } - } -} - type RPCRequestDurationLimiter struct { requestDurationLimiter diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go index b22970f84..934443b9f 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go @@ -2,7 +2,6 @@ package network import ( "context" - "io" "net" "net/http" "testing" @@ -42,132 +41,6 @@ func createTestServer(ctx context.Context) (string, *TestServerHandlerWrapper, c } } -func TestHTTPRequestDurationLimiter_Limiting(t *testing.T) { - ctx := t.Context() - addr, redirector, shutdown := createTestServer(ctx) - longExecutingHandler := &TestServerHandlerWrapper{ - f: func(res http.ResponseWriter, req *http.Request) { - select { - case <-req.Context().Done(): - return - case <-time.After(time.Second * 10): - } - n, err := res.Write([]byte{1, 2, 3}) - require.Equal(t, 3, n) - require.NoError(t, err) - }, - } - warningCounter := TestingCounter{} - limitCounter := TestingCounter{} - logCounter := makeTestLogCounter() - redirector.f = MakeHTTPRequestDurationLimiter( - longExecutingHandler, - time.Second/20, - time.Second/10, - &warningCounter, - &limitCounter, - logCounter.Entry()).ServeHTTP - - client := http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr+"/", nil) - require.NoError(t, err) - resp, err := client.Do(req) - require.NoError(t, err) - bytes, err := io.ReadAll(resp.Body) - require.NoError(t, resp.Body.Close()) - require.NoError(t, err) - require.Equal(t, []byte{}, bytes) - require.Equal(t, http.StatusGatewayTimeout, resp.StatusCode) - require.Zero(t, warningCounter.count) - require.Equal(t, int64(1), limitCounter.count) - require.Equal(t, [7]int{0, 0, 0, 0, 1, 0, 0}, logCounter.writtenLogEntries) - shutdown() -} - -func TestHTTPRequestDurationLimiter_NoLimiting(t *testing.T) { - ctx := t.Context() - addr, redirector, shutdown := createTestServer(ctx) - longExecutingHandler := &TestServerHandlerWrapper{ - f: func(res http.ResponseWriter, req *http.Request) { - select { - case <-req.Context().Done(): - return - case <-time.After(time.Second / 10): - } - n, err := res.Write([]byte{1, 2, 3}) - require.Equal(t, 3, n) - require.NoError(t, err) - }, - } - warningCounter := TestingCounter{} - limitCounter := TestingCounter{} - logCounter := makeTestLogCounter() - redirector.f = MakeHTTPRequestDurationLimiter( - longExecutingHandler, - time.Second*5, - time.Second*10, - &warningCounter, - &limitCounter, - logCounter.Entry()).ServeHTTP - - client := http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr+"/", nil) - require.NoError(t, err) - resp, err := client.Do(req) - require.NoError(t, err) - bytes, err := io.ReadAll(resp.Body) - require.NoError(t, resp.Body.Close()) - require.NoError(t, err) - require.Equal(t, []byte{1, 2, 3}, bytes) - require.Equal(t, http.StatusOK, resp.StatusCode) - require.Zero(t, warningCounter.count) - require.Zero(t, limitCounter.count) - require.Equal(t, [7]int{0, 0, 0, 0, 0, 0, 0}, logCounter.writtenLogEntries) - shutdown() -} - -func TestHTTPRequestDurationLimiter_NoLimiting_Warn(t *testing.T) { - ctx := t.Context() - addr, redirector, shutdown := createTestServer(ctx) - longExecutingHandler := &TestServerHandlerWrapper{ - f: func(res http.ResponseWriter, req *http.Request) { - select { - case <-req.Context().Done(): - return - case <-time.After(time.Second / 5): - } - n, err := res.Write([]byte{1, 2, 3}) - require.Equal(t, 3, n) - require.NoError(t, err) - }, - } - warningCounter := TestingCounter{} - limitCounter := TestingCounter{} - logCounter := makeTestLogCounter() - redirector.f = MakeHTTPRequestDurationLimiter( - longExecutingHandler, - time.Second/10, - time.Second*10, - &warningCounter, - &limitCounter, - logCounter.Entry()).ServeHTTP - - client := http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr+"/", nil) - require.NoError(t, err) - resp, err := client.Do(req) - require.NoError(t, err) - bytes, err := io.ReadAll(resp.Body) - require.NoError(t, resp.Body.Close()) - require.NoError(t, err) - require.Equal(t, []byte{1, 2, 3}, bytes) - require.Equal(t, http.StatusOK, resp.StatusCode) - require.Equal(t, int64(1), warningCounter.count) - require.Zero(t, limitCounter.count) - require.Equal(t, [7]int{0, 0, 0, 0, 1, 0, 0}, logCounter.writtenLogEntries) - shutdown() -} - type JRPCHandlerFunc func(ctx context.Context, r *jrpc2.Request) (any, error) func bindRPCHoist(redirector *TestServerHandlerWrapper) *JRPCHandlerFunc { @@ -314,34 +187,3 @@ func TestJRPCRequestDurationLimiter_NoLimiting_Warn(t *testing.T) { shutdown() } -func TestHTTPRequestDurationLimiter_Panicing(t *testing.T) { - ctx := t.Context() - addr, redirector, shutdown := createTestServer(ctx) - longExecutingHandler := &TestServerHandlerWrapper{ - f: func(_ http.ResponseWriter, _ *http.Request) { - panic("test panic") - }, - } - - logCounter := makeTestLogCounter() - redirector.f = MakeHTTPRequestDurationLimiter( - longExecutingHandler, - time.Second*10, - time.Second*10, - nil, - nil, - logCounter.Entry()).ServeHTTP - - client := http.Client{} - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+addr+"/", nil) - require.NoError(t, err) - resp, err := client.Do(req) - require.NoError(t, err) - bytes, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - require.Equal(t, http.StatusInternalServerError, resp.StatusCode) - require.Equal(t, []byte{}, bytes) - require.Equal(t, [7]int{0, 0, 0, 7, 0, 0, 0}, logCounter.writtenLogEntries) - shutdown() -} From cff73e2d2f7ffed08c6c1de9bace5d7635b27925 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 14:03:18 -0400 Subject: [PATCH 18/25] create independent install-stellar-core.sh file, use in perf eval --- .../perf-eval/backfill-test/run-backfill.sh | 3 ++- .../backfill-test/runner/instantiate.go | 2 +- .../perf-eval/bootstrap-common.sh | 14 ---------- .../ingest-load-test/run-load-test.sh | 3 ++- scripts/install-stellar-core.sh | 27 +++++++++++++++++++ 5 files changed, 32 insertions(+), 17 deletions(-) create mode 100755 scripts/install-stellar-core.sh diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh index cbc3ef2f9..a1b85475a 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/run-backfill.sh @@ -7,5 +7,6 @@ LEG_TITLE="Backfill ingestion" bootstrap_box -install_core +log "installing stellar-core" +./scripts/install-stellar-core.sh run_leg ./cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go index 240101210..e66683aa1 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test/runner/instantiate.go @@ -25,7 +25,7 @@ import ( const ( // runner runs w/ cwd = repo root, so paths are relative to there legDir = "cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/backfill-test" - corePath = "/usr/bin/stellar-core" // apt-installed by install_core + corePath = "/usr/bin/stellar-core" // apt-installed by scripts/install-stellar-core.sh // daemon output; the box log's console/syslog tee drains at ~45 KB/s, which throttled request logging daemonLogPath = "/var/log/stellar-rpc.log" ) diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh index ebdcadadd..21c102c39 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/bootstrap-common.sh @@ -120,20 +120,6 @@ bootstrap_box() { log "checked out $TARGET_SHA; handing off to the Go runner" } -# install_core installs the latest stable stellar-core, so captive core keeps up with pubnet protocol upgrades. -install_core() { - log "installing stellar-core" - local codename - codename=$(. /etc/os-release && echo "$VERSION_CODENAME") - curl -fsSL https://apt.stellar.org/SDF.asc -o /etc/apt/trusted.gpg.d/SDF.asc - # the core package links libc++ 20, which comes from apt.llvm.org - curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc - echo "deb https://apt.stellar.org $codename stable" > /etc/apt/sources.list.d/SDF.list - echo "deb http://apt.llvm.org/$codename/ llvm-toolchain-$codename-20 main" > /etc/apt/sources.list.d/llvm.list - apt-get update -qq -o Acquire::Retries=3 - apt-get install -y -qq --no-install-recommends -o Acquire::Retries=3 stellar-core -} - # run_leg hands off to a leg's runner package. The Go runner owns the # verdict from here, so the bootstrap ERR trap is released first. On a non-zero # exit the runner has written the failure body to RESULTS_FILE (or died before diff --git a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh index b62b74f1b..e40752d1a 100644 --- a/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh +++ b/cmd/stellar-rpc/internal/rpcv1/integrationtest/infrastructure/perf-eval/ingest-load-test/run-load-test.sh @@ -7,7 +7,8 @@ log "clearing stale apply-load state" rm -f /tmp/bench-results.json /tmp/load-test-ledgers-*.xdr.zstd bootstrap_box -install_core +log "installing stellar-core" +./scripts/install-stellar-core.sh # The integration test binary links rpcv2's grocksdb + zstd; build them like CI's setup-go does. log "building native libs (zstd, rocksdb)" diff --git a/scripts/install-stellar-core.sh b/scripts/install-stellar-core.sh new file mode 100755 index 000000000..604652937 --- /dev/null +++ b/scripts/install-stellar-core.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Install stellar-core from SDF's apt repo (stable channel), plus the +# apt.llvm.org repo for the libc++ its package links. Pass a package version +# to pin it, else the newest release is installed. Run as root. +# +# ./scripts/install-stellar-core.sh # newest +# ./scripts/install-stellar-core.sh 28.0.1-3508.947aad841.noble # pinned +# +set -euo pipefail + +# Bump when core's package moves to a newer libc++ major. +LLVM_VERSION=20 +CORE_VERSION="${1:-}" + +CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") + +curl -fsSL https://apt.stellar.org/SDF.asc -o /etc/apt/trusted.gpg.d/SDF.asc +curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc +echo "deb https://apt.stellar.org $CODENAME stable" > /etc/apt/sources.list.d/SDF.list +echo "deb http://apt.llvm.org/$CODENAME/ llvm-toolchain-$CODENAME-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list + +apt-get update -qq -o Acquire::Retries=3 +# core's postinst calls adduser without depending on it (bare images lack it) +apt-get install -y -qq --no-install-recommends -o Acquire::Retries=3 \ + adduser "stellar-core${CORE_VERSION:+=$CORE_VERSION}" +stellar-core version From bbf1e3bc8a223dada2fad2f09eeddd085ed0f006 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 14:05:50 -0400 Subject: [PATCH 19/25] migrate intergration test yml to use install core script --- .github/workflows/integration-tests.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 7246f0ef8..e5ac9c1e8 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -110,16 +110,7 @@ jobs: - name: Install Captive Core from debian package if: env.CORE_GIT_REF == '' shell: bash - run: | - sudo wget -qO - https://apt.stellar.org/SDF.asc | APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=true sudo apt-key add - - sudo bash -c 'echo "deb https://apt.stellar.org noble stable" > /etc/apt/sources.list.d/SDF.list' - - # The noble package links libc++ 20, which comes from apt.llvm.org. - sudo wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key - sudo bash -c 'echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" > /etc/apt/sources.list.d/llvm.list' - - sudo apt-get update && sudo apt-get install -y stellar-core="$CORE_DEBIAN_PKG_VERSION" - echo "Using stellar core version $(stellar-core version)" + run: sudo ./scripts/install-stellar-core.sh "$CORE_DEBIAN_PKG_VERSION" # Docker-compose's remote contexts on Ubuntu 20 started failing with an OpenSSL versioning error. # See https://stackoverflow.com/questions/66579446/error-executing-docker-compose-building-webserver-unable-to-prepare-context-un From 1a84f58549d42b742f40f7b50890dcb60a7e6d84 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 14:22:50 -0400 Subject: [PATCH 20/25] migrate dockerfiles + modify install script for dockerfiles --- cmd/stellar-rpc/docker/Dockerfile | 12 ++---------- cmd/stellar-rpc/docker/Dockerfile.rpcv2 | 6 ++---- scripts/install-stellar-core.sh | 12 ++++++++---- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/cmd/stellar-rpc/docker/Dockerfile b/cmd/stellar-rpc/docker/Dockerfile index 81dae8424..1b96eb933 100644 --- a/cmd/stellar-rpc/docker/Dockerfile +++ b/cmd/stellar-rpc/docker/Dockerfile @@ -31,16 +31,8 @@ ENV DEBIAN_FRONTEND=noninteractive # ca-certificates are required to make tls connections RUN apt update && apt install -y --no-install-recommends ca-certificates curl wget gnupg apt-utils jq -RUN curl -sSL https://apt.stellar.org/SDF.asc | gpg --dearmor >/etc/apt/trusted.gpg.d/SDF.gpg -RUN echo "deb https://apt.stellar.org noble stable" >/etc/apt/sources.list.d/SDF.list -RUN echo "deb https://apt.stellar.org noble unstable" >/etc/apt/sources.list.d/SDF-unstable.list - -# install llvm-20 so that core can be installed -RUN wget -O /etc/apt/trusted.gpg.d/apt.llvm.org.asc https://apt.llvm.org/llvm-snapshot.gpg.key -RUN echo 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' > /etc/apt/sources.list.d/llvm.list -RUN echo 'deb-src http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' >> /etc/apt/sources.list.d/llvm.list - -RUN apt update && apt install -y --no-install-recommends stellar-core=${STELLAR_CORE_VERSION} +COPY scripts/install-stellar-core.sh /tmp/install-stellar-core.sh +RUN CHANNELS="stable unstable" /tmp/install-stellar-core.sh "${STELLAR_CORE_VERSION}" RUN apt clean # Copy the binary from the build stage diff --git a/cmd/stellar-rpc/docker/Dockerfile.rpcv2 b/cmd/stellar-rpc/docker/Dockerfile.rpcv2 index afe21910b..50ddfa359 100644 --- a/cmd/stellar-rpc/docker/Dockerfile.rpcv2 +++ b/cmd/stellar-rpc/docker/Dockerfile.rpcv2 @@ -69,10 +69,8 @@ RUN apt update && apt install -y --no-install-recommends \ && apt clean # stellar-core from SDF apt repo (noble packages). -RUN curl -sSL https://apt.stellar.org/SDF.asc | gpg --dearmor >/etc/apt/trusted.gpg.d/SDF.gpg -RUN echo "deb https://apt.stellar.org noble stable" >/etc/apt/sources.list.d/SDF.list -RUN echo "deb https://apt.stellar.org noble unstable" >/etc/apt/sources.list.d/SDF-unstable.list -RUN apt update && apt install -y --no-install-recommends stellar-core=${STELLAR_CORE_VERSION} +COPY scripts/install-stellar-core.sh /tmp/install-stellar-core.sh +RUN CHANNELS="stable unstable" /tmp/install-stellar-core.sh "${STELLAR_CORE_VERSION}" RUN apt clean COPY --from=zstd-build /usr/local/lib/libzstd.so* /usr/local/lib/ diff --git a/scripts/install-stellar-core.sh b/scripts/install-stellar-core.sh index 604652937..2fb38fd05 100755 --- a/scripts/install-stellar-core.sh +++ b/scripts/install-stellar-core.sh @@ -1,23 +1,27 @@ #!/usr/bin/env bash # -# Install stellar-core from SDF's apt repo (stable channel), plus the -# apt.llvm.org repo for the libc++ its package links. Pass a package version -# to pin it, else the newest release is installed. Run as root. +# Install stellar-core from SDF's apt repo, plus the apt.llvm.org repo for the +# libc++ its package links. Pass a package version to pin it, else the newest +# release in CHANNELS (default stable) is installed. Run as root. # # ./scripts/install-stellar-core.sh # newest # ./scripts/install-stellar-core.sh 28.0.1-3508.947aad841.noble # pinned +# CHANNELS="stable unstable" ./scripts/install-stellar-core.sh # + prerelease builds # set -euo pipefail # Bump when core's package moves to a newer libc++ major. LLVM_VERSION=20 CORE_VERSION="${1:-}" +CHANNELS="${CHANNELS:-stable}" CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME") curl -fsSL https://apt.stellar.org/SDF.asc -o /etc/apt/trusted.gpg.d/SDF.asc curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc -echo "deb https://apt.stellar.org $CODENAME stable" > /etc/apt/sources.list.d/SDF.list +for c in $CHANNELS; do + echo "deb https://apt.stellar.org $CODENAME $c" +done > /etc/apt/sources.list.d/SDF.list echo "deb http://apt.llvm.org/$CODENAME/ llvm-toolchain-$CODENAME-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list apt-get update -qq -o Acquire::Retries=3 From 476f401b841212c48f99f523cacc733463496902 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 15:11:42 -0400 Subject: [PATCH 21/25] remove branch push trigger --- .github/workflows/load-test-coordinator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/load-test-coordinator.yml b/.github/workflows/load-test-coordinator.yml index c191242ea..6342b221a 100644 --- a/.github/workflows/load-test-coordinator.yml +++ b/.github/workflows/load-test-coordinator.yml @@ -11,7 +11,7 @@ defaults: on: push: - branches: [release/**, bypass-jrpc2-marshal] + branches: [release/**] workflow_dispatch: inputs: target_ref: From cf2b1ccd95a5b75bfc4b732e630e28cf944a0fde Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 16:46:50 -0400 Subject: [PATCH 22/25] wait in Handler.Close for handlers a per-method duration limit stopped waiting for --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 16 +++++++++++----- cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go | 3 ++- .../internal/network/requestdurationlimiter.go | 8 ++++++-- .../network/requestdurationlimiter_test.go | 11 +++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index e3438eff2..fdf224847 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -12,6 +12,7 @@ import ( "net/http" "strconv" "strings" + "sync" "time" "unicode" @@ -51,13 +52,15 @@ const ( type Handler struct { http.Handler - bridge *bridge + bridge *bridge + inflight *sync.WaitGroup } // Close stops accepting JSON-RPC requests, cancels those in flight and waits // for their handlers to return, so none outlives the stores closed after it. func (h Handler) Close() { h.bridge.Close() + h.inflight.Wait() // handlers a duration limiter stopped waiting for } // HandlerSpec describes one JSON-RPC method: its handler plus the per-method @@ -178,7 +181,7 @@ func toSnakeCase(s string) string { // wrapWithLimiters applies the per-method backlog-queue and request-duration // limiters (and their metrics) around a single method handler. -func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry) jrpc2.Handler { +func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry, inflight *sync.WaitGroup) jrpc2.Handler { longName := toSnakeCase(spec.MethodName) queueLimiterGaugeName := longName + "_inflight_requests" queueLimiterGaugeHelp := "Number of concurrenty in-flight " + spec.MethodName + " requests" @@ -215,6 +218,7 @@ func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry) j requestDurationWarn := spec.RequestDurationLimit / warningThresholdDenominator durationLimiter := network.MakeJrpcRequestDurationLimiter( queueLimiter.Handle, + inflight, requestDurationWarn, spec.RequestDurationLimit, requestDurationWarnCounter, @@ -234,8 +238,9 @@ func wrapWithLimiters(spec HandlerSpec, daemon host.Daemon, logger *log.Entry) j // NewHandler constructs a Handler instance from the given method specs func NewHandler(params Params) Handler { handlersMap := handler.Map{} + inflight := new(sync.WaitGroup) for _, spec := range params.Specs { - handlersMap[spec.MethodName] = wrapWithLimiters(spec, params.Daemon, params.Logger) + handlersMap[spec.MethodName] = wrapWithLimiters(spec, params.Daemon, params.Logger, inflight) } globalQueueRequestExecutionDurationWarningCounter := prometheus.NewCounter(prometheus.CounterOpts{ @@ -280,7 +285,8 @@ func NewHandler(params Params) Handler { }) return Handler{ - bridge: rpc, - Handler: corsMiddleware.Handler(handler), + bridge: rpc, + inflight: inflight, + Handler: corsMiddleware.Handler(handler), } } diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go index c46c3701f..50d91e10d 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc_test.go @@ -2,6 +2,7 @@ package jsonrpc import ( "context" + "sync" "testing" "time" @@ -37,7 +38,7 @@ func TestWrapWithLimiters_ClientDisconnect(t *testing.T) { }, QueueLimit: 1, RequestDurationLimit: time.Minute, - }, host.MakeNoOpDaemon(), log.DefaultLogger) + }, host.MakeNoOpDaemon(), log.DefaultLogger, new(sync.WaitGroup)) req := (&jrpc2.ParsedRequest{ID: "1", Method: test.method}).ToRequest() _, err := h(ctx, req) diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go index 6546221b8..b820003db 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter.go @@ -3,6 +3,7 @@ package network import ( "context" "math" + "sync" "time" "github.com/creachadair/jrpc2" @@ -35,10 +36,12 @@ type RPCRequestDurationLimiter struct { requestDurationLimiter jrpcDownstreamHandler jrpc2.Handler + inflight *sync.WaitGroup // downstream calls, which can outlive Handle past the limit } func MakeJrpcRequestDurationLimiter( downstream jrpc2.Handler, + inflight *sync.WaitGroup, warningThreshold time.Duration, limitThreshold time.Duration, warningCounter increasingCounter, @@ -52,6 +55,7 @@ func MakeJrpcRequestDurationLimiter( return &RPCRequestDurationLimiter{ jrpcDownstreamHandler: downstream, + inflight: inflight, requestDurationLimiter: requestDurationLimiter{ warningThreshold: warningThreshold, limitThreshold: limitThreshold, @@ -87,7 +91,7 @@ func (q *RPCRequestDurationLimiter) Handle(ctx context.Context, req *jrpc2.Reque requestCtx, requestCtxCancel := context.WithTimeout(ctx, q.limitThreshold) defer requestCtxCancel() - go func() { + q.inflight.Go(func() { defer func() { if err := recover(); err != nil { q.logger.Errorf("Request for method %s resulted in an error : %v", req.Method(), err) @@ -97,7 +101,7 @@ func (q *RPCRequestDurationLimiter) Handle(ctx context.Context, req *jrpc2.Reque var res requestResultOutput res.data, res.err = q.jrpcDownstreamHandler(requestCtx, req) requestCompleted <- res - }() + }) warn := false for { diff --git a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go index 934443b9f..66768c7ec 100644 --- a/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go +++ b/cmd/stellar-rpc/internal/network/requestdurationlimiter_test.go @@ -4,6 +4,8 @@ import ( "context" "net" "net/http" + "sync" + "sync/atomic" "testing" "time" @@ -61,9 +63,12 @@ func TestJRPCRequestDurationLimiter_Limiting(t *testing.T) { addr, redirector, shutdown := createTestServer(ctx) hoistFunction := bindRPCHoist(redirector) + var returned atomic.Bool longExecutingHandler := handler.New(func(ctx context.Context, _ *jrpc2.Request) (any, error) { select { case <-ctx.Done(): + time.Sleep(time.Second / 20) // still unwinding after the limiter has answered + returned.Store(true) return nil, ctx.Err() case <-time.After(time.Second * 10): } @@ -73,8 +78,10 @@ func TestJRPCRequestDurationLimiter_Limiting(t *testing.T) { warningCounter := TestingCounter{} limitCounter := TestingCounter{} logCounter := makeTestLogCounter() + inflight := new(sync.WaitGroup) *hoistFunction = MakeJrpcRequestDurationLimiter( longExecutingHandler, + inflight, time.Second/20, time.Second/10, &warningCounter, @@ -98,6 +105,8 @@ func TestJRPCRequestDurationLimiter_Limiting(t *testing.T) { require.Zero(t, warningCounter.count) require.Equal(t, int64(1), limitCounter.count) require.Equal(t, [7]int{0, 0, 0, 0, 1, 0, 0}, logCounter.writtenLogEntries) + inflight.Wait() + require.True(t, returned.Load(), "inflight did not wait for the handler") shutdown() } @@ -121,6 +130,7 @@ func TestJRPCRequestDurationLimiter_NoLimiting(t *testing.T) { logCounter := makeTestLogCounter() *hoistFunction = MakeJrpcRequestDurationLimiter( longExecutingHandler, + new(sync.WaitGroup), time.Second*5, time.Second*10, &warningCounter, @@ -164,6 +174,7 @@ func TestJRPCRequestDurationLimiter_NoLimiting_Warn(t *testing.T) { logCounter := makeTestLogCounter() *hoistFunction = MakeJrpcRequestDurationLimiter( longExecutingHandler, + new(sync.WaitGroup), time.Second/10, time.Second*10, &warningCounter, From ac5bb2d2e88866f3ef261171a0d806aad98c623b Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 16:52:48 -0400 Subject: [PATCH 23/25] answer a panicking call with -32003 instead of failing the whole request --- cmd/stellar-rpc/internal/jsonrpc/bridge.go | 26 ++++++------------- .../internal/jsonrpc/bridge_test.go | 21 +++++++++------ 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge.go b/cmd/stellar-rpc/internal/jsonrpc/bridge.go index 300609043..af1cfc888 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/bridge.go +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge.go @@ -15,7 +15,6 @@ import ( "strconv" "strings" "sync" - "sync/atomic" "time" "github.com/creachadair/jrpc2" @@ -162,22 +161,15 @@ func (l durationLimits) timedOut(r *http.Request, elapsed time.Duration) bool { return false } -// handlerPanic is the call stack of a panic recovered in a handler's call. -type handlerPanic []string - -// recoverPanic answers 500 to a request whose serving panicked and logs the -// stack, as network's HTTP limiter did. +// recoverPanic answers 500 to a request whose serving panicked outside a +// handler call and logs the stack, as network's HTTP limiter did. func (b *bridge) recoverPanic(w http.ResponseWriter) { p := recover() if p == nil { return } - stack, ok := p.(handlerPanic) - if !ok { - stack = util.CallStack(p, "", "(*bridge).ServeHTTP", 8) - } w.WriteHeader(http.StatusInternalServerError) - for _, line := range stack { + for _, line := range util.CallStack(p, "", "(*bridge).ServeHTTP", 8) { b.limits.logger.Warn(line) } } @@ -204,7 +196,6 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo rsps := make([]*response, len(reqs)) var wg sync.WaitGroup - var crashed atomic.Pointer[handlerPanic] for i, pr := range reqs { method, rsp := b.route(ctx, pr) if method == nil { @@ -218,9 +209,11 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo call := func() { defer b.sem.Release(1) defer func() { - if p := recover(); p != nil { - stack := handlerPanic(util.CallStack(p, pr.Method, "(*bridge).run.func", 8)) - crashed.CompareAndSwap(nil, &stack) + if p := recover(); p != nil { // answer just this call, as the duration limiter does + for _, line := range util.CallStack(p, pr.Method, "(*bridge).run.func", 8) { + b.limits.logger.Warn(line) + } + rsps[i] = respond(pr, nil, network.ErrFailToProcessDueToInternalIssue) } }() v, err := method(ctx, pr.ToRequest()) @@ -233,9 +226,6 @@ func (b *bridge) run(ctx context.Context, reqs []*jrpc2.ParsedRequest) ([]*respo } } wg.Wait() - if stack := crashed.Load(); stack != nil { - panic(*stack) // recoverPanic answers 500 for the whole request - } return slices.DeleteFunc(rsps, func(r *response) bool { return r == nil }), nil } diff --git a/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go index df3b18644..97450effa 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go +++ b/cmd/stellar-rpc/internal/jsonrpc/bridge_test.go @@ -271,24 +271,29 @@ func TestBridge_DurationLimits(t *testing.T) { } } -// A panic while serving is answered 500 and its stack logged, whether the -// handler ran on the request's goroutine or on its own. +// A handler panic answers just that call with -32003 and its stack is logged, +// whether the handler ran on the request's goroutine or on its own. func TestBridge_Panic(t *testing.T) { methods := handler.Map{ "panic": handler.New(func(context.Context) (any, error) { panic("test panic") }), "ok": handler.New(func(context.Context) (any, error) { return 1, nil }), } - for name, body := range map[string]string{ - "single": `{"jsonrpc":"2.0","id":1,"method":"panic"}`, - "batch element": `[{"jsonrpc":"2.0","id":1,"method":"panic"},{"jsonrpc":"2.0","id":2,"method":"ok"}]`, + failed := `{"jsonrpc":"2.0","id":1,"error":` + + `{"code":-32003,"message":"[-32003] request failed to process due to internal issue"}}` + for name, tc := range map[string]struct{ body, want string }{ + "single": {`{"jsonrpc":"2.0","id":1,"method":"panic"}`, failed}, + "batch element": { + `[{"jsonrpc":"2.0","id":1,"method":"panic"},{"jsonrpc":"2.0","id":2,"method":"ok"}]`, + `[` + failed + `,{"jsonrpc":"2.0","id":2,"result":1}]`, + }, } { t.Run(name, func(t *testing.T) { logger, hook := testLogger() b := newBridge(methods, durationLimits{limit: network.RequestDurationLimiterNoLimit, logger: logger}) t.Cleanup(b.Close) - rec := postBridge(t, b, body) - require.Equal(t, http.StatusInternalServerError, rec.Code) - require.Empty(t, rec.Body.String()) + rec := postBridge(t, b, tc.body) + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, tc.want, rec.Body.String()) entries := hook.AllEntries() require.NotEmpty(t, entries) require.Equal(t, "test panic when calling panic", entries[0].Message) From fe8754cedfd03fec7df6b545df8503672cd7053f Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 16:58:30 -0400 Subject: [PATCH 24/25] skip cancellation error logs by checking the error, as getEvents does --- cmd/stellar-rpc/internal/methods/get_ledgers.go | 2 +- cmd/stellar-rpc/internal/methods/get_version_info.go | 4 +++- cmd/stellar-rpc/internal/preflight/preflight.go | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/stellar-rpc/internal/methods/get_ledgers.go b/cmd/stellar-rpc/internal/methods/get_ledgers.go index babe9d3d4..5cdce2b92 100644 --- a/cmd/stellar-rpc/internal/methods/get_ledgers.go +++ b/cmd/stellar-rpc/internal/methods/get_ledgers.go @@ -73,7 +73,7 @@ func (h ledgersHandler) getLedgers( dsRange, err := h.datastoreLedgerReader.GetAvailableLedgerRange(ctx) if err != nil { // log error but continue using local ledger range; a canceled request is not a fault - if ctx.Err() == nil { + if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { h.logger.WithError(err).Error("failed to get available ledger range from datastore") } } else { diff --git a/cmd/stellar-rpc/internal/methods/get_version_info.go b/cmd/stellar-rpc/internal/methods/get_version_info.go index 027941683..22388ebf7 100644 --- a/cmd/stellar-rpc/internal/methods/get_version_info.go +++ b/cmd/stellar-rpc/internal/methods/get_version_info.go @@ -2,6 +2,7 @@ package methods import ( "context" + "errors" "github.com/creachadair/jrpc2" @@ -24,7 +25,8 @@ func NewGetVersionInfoHandler( // version once it starts core, which is after handlers are built. captiveCoreVersion := daemon.CoreVersion() protocolVersion, err := getProtocolVersion(ctx, ledgerReader) - if err != nil && ctx.Err() == nil { // a canceled request is not a fault + // a canceled request is not a fault + if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { logger.WithError(err).Error("failed to fetch protocol version") } diff --git a/cmd/stellar-rpc/internal/preflight/preflight.go b/cmd/stellar-rpc/internal/preflight/preflight.go index c977a84f5..5401548ec 100644 --- a/cmd/stellar-rpc/internal/preflight/preflight.go +++ b/cmd/stellar-rpc/internal/preflight/preflight.go @@ -19,6 +19,7 @@ import "C" import ( "context" + "errors" "fmt" "runtime/cgo" "unsafe" @@ -56,7 +57,8 @@ func SnapshotSourceGet(handle C.uintptr_t, cLedgerKey C.xdr_t) C.ledger_entry_an } entries, _, err := h.ledgerEntryGetter.GetLedgerEntries(h.ctx, []xdr.LedgerKey{ledgerKey}) if err != nil { - if h.ctx.Err() == nil { // a canceled request is not a fault + // a canceled request is not a fault + if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { h.logger.WithError(err).Error("SnapshotSourceGet(): GetLedgerEntries() failed") } return C.ledger_entry_and_ttl_t{} From 5ce6f3dad49b903dd93918bff13e6f3d90c7b529 Mon Sep 17 00:00:00 2001 From: Christian Jonas Date: Fri, 9 Oct 2026 17:05:33 -0400 Subject: [PATCH 25/25] log the caller's JSON-RPC ID as json_req when a request finishes --- cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go index fdf224847..6273e9365 100644 --- a/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go +++ b/cmd/stellar-rpc/internal/jsonrpc/jsonrpc.go @@ -135,7 +135,7 @@ func decorateHandlers(daemon host.Daemon, logger *log.Entry, m handler.Map) hand label[labelStatus] = "canceled" } requestMetric.With(label).Observe(duration.Seconds()) - logResponse(logger, reqID, duration, label[labelStatus]) + logResponse(logger, reqID, r.ID(), duration, label[labelStatus]) return result, err }) } @@ -156,12 +156,12 @@ func logRequest(logger *log.Entry, reqID string, req *jrpc2.Request) { logger.Debug("starting JSONRPC request params") } -func logResponse(logger *log.Entry, reqID string, duration time.Duration, status string) { +func logResponse(logger *log.Entry, reqID, jsonReq string, duration time.Duration, status string) { logger = logger.WithFields(log.F{ "subsys": "jsonrpc", "req": reqID, "duration": duration.String(), - "json_req": reqID, + "json_req": jsonReq, "status": status, }) logger.Info("finished JSONRPC request")