diff --git a/cmd/stellar-rpc/internal/integrationtest/infrastructure/test.go b/cmd/stellar-rpc/internal/integrationtest/infrastructure/test.go index 36fdb186f..91161a8af 100644 --- a/cmd/stellar-rpc/internal/integrationtest/infrastructure/test.go +++ b/cmd/stellar-rpc/internal/integrationtest/infrastructure/test.go @@ -595,6 +595,12 @@ func (i *Test) waitForRPC() { if i.daemon != nil { exited = i.daemon.exited() } + // Core builds its Soroban transaction queue only when a ledger closes + // through consensus, not during catch-up. The limits upgrade is a Soroban + // transaction, so wait for one consensus ledger before setup sends it. + // The load test has no Core; the delayed-daemon mode is behind on purpose. + var caughtUp uint32 + needOneMore := i.coreClient != nil && i.delayDaemonForLedgerN == 0 for { select { case err := <-exited: @@ -603,8 +609,18 @@ func (i *Test) waitForRPC() { } result, err := i.GetRPCLient().GetHealth(i.t.Context()) i.t.Logf("getHealth: %+v; err: %v", result, err) - if err == nil && result.Status == "healthy" && i.caughtUpWithCore(result.LatestLedger) { - return + if err == nil && result.Status == "healthy" { + switch { + case caughtUp != 0: + if result.LatestLedger > caughtUp { + return + } + case i.caughtUpWithCore(result.LatestLedger): + if !needOneMore { + return + } + caughtUp = result.LatestLedger + } } require.False(i.t, time.Now().After(deadline), "RPC never got healthy: %+v", err) time.Sleep(time.Second) @@ -973,6 +989,15 @@ func (i *Test) CreateHelloWorldContract() (protocol.GetTransactionResponse, [32] return i.PreflightAndSendMasterOperation(op), contractID, contractHash } +func (i *Test) CreateEventsContract() (protocol.GetTransactionResponse, [32]byte, xdr.Hash) { + _, contractHash := i.uploadContract(GetEventsContract()) + salt := xdr.Uint256(testSalt) + account := i.MasterAccount().GetAccountID() + op := createCreateContractV2Operation(account, salt, contractHash) + contractID := GetContractID(i.t, account, salt, StandaloneNetworkPassphrase) + return i.PreflightAndSendMasterOperation(op), contractID, contractHash +} + func (i *Test) CreateAutorestoreContract(items int) (protocol.GetTransactionResponse, [32]byte, xdr.Hash) { contractBinary := GetAutorestoreContract() _, contractHash := i.uploadContract(contractBinary) @@ -1058,6 +1083,7 @@ func (i *Test) upgradeLimitsWithFile(limitFile, expectInSorobanInfo string) { txnCount := len(lines) / 2 // each upgrade command outputs txnB64 \n hash assert.Len(i.t, lines, 9) + var lastLedger uint32 for j := 0; j+1 < len(lines); j += 2 { b64 := lines[j] i.t.Logf("Upgrade transaction: %s (hash: %s)", b64, lines[j+1]) @@ -1068,9 +1094,14 @@ func (i *Test) upgradeLimitsWithFile(limitFile, expectInSorobanInfo string) { txn, t := gtxn.Transaction() require.True(i.t, t) - SendSuccessfulTransaction(i.t, i.rpcClient, nil /* signed @ L791 */, txn) + lastLedger = SendSuccessfulTransaction(i.t, i.rpcClient, nil /* signed @ L791 */, txn).Ledger } + // The daemon reported the ledger from its captive core. The upgrade key + // below is resolved by the Core container against its own last closed + // ledger, which can still be behind under load. + i.waitForCoreAtLedger(int(lastLedger)) + upgradeKey := strings.TrimSpace(lines[len(lines)-1]) i.t.Logf("Upgrading Core config with key: %s", upgradeKey) diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/conversions_test.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/conversions_test.go index 240744738..b089491eb 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/conversions_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/conversions_test.go @@ -283,7 +283,7 @@ func TestEventFilterFields(t *testing.T) { func TestEventFilterJSONFormat(t *testing.T) { _, transfer := symbolScVal(t, "transfer") - t.Run("with a topic is rejected until #940", func(t *testing.T) { + t.Run("with a topic is rejected", func(t *testing.T) { in := protocol.EventFilterV2{Topic0: requestTopic(t, transfer)} _, err := eventFilter(&in, protocol.FormatJSON) require.ErrorIs(t, err, errJSONInputFormatUnsupported) diff --git a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v2.go b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v2.go index ac6c7b188..83c3d72a3 100644 --- a/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v2.go +++ b/cmd/stellar-rpc/internal/rpcv2/eventsapi/get_events_v2.go @@ -351,9 +351,9 @@ func jrpcError(message string, data any) error { return &jrpc2.Error{Code: jrpc2.InvalidParams, Message: message, Data: encoded} } -// errJSONInputFormatUnsupported: xdrInputFormat "json" needs a JSON-to-XDR -// converter, because the term index matches on a topic's canonical bytes. -// Deferred to #940. +// errJSONInputFormatUnsupported: xdrInputFormat "json" is a spec option this +// server does not serve. The term index matches on a topic's canonical XDR +// bytes; xdr2json can produce them, but the handler is not wired to it. var errJSONInputFormatUnsupported = errors.New( "xdrInputFormat \"json\" is not supported yet") diff --git a/cmd/stellar-rpc/internal/rpcv2/integrationtest/get_events_v2_test.go b/cmd/stellar-rpc/internal/rpcv2/integrationtest/get_events_v2_test.go new file mode 100644 index 000000000..bc9bc8210 --- /dev/null +++ b/cmd/stellar-rpc/internal/rpcv2/integrationtest/get_events_v2_test.go @@ -0,0 +1,355 @@ +package integrationtest + +import ( + "encoding/json" + "testing" + + "github.com/creachadair/jrpc2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + client "github.com/stellar/go-stellar-sdk/clients/rpcclient" + protocol "github.com/stellar/go-stellar-sdk/protocols/rpc" + "github.com/stellar/go-stellar-sdk/strkey" + "github.com/stellar/go-stellar-sdk/xdr" + + "github.com/stellar/stellar-rpc/cmd/stellar-rpc/internal/integrationtest/infrastructure" +) + +// One increment leaves three events: fee, the contract's own, fee refund. +const eventsPerInvoke = 3 + +type eventsFixture struct { + contractID string // strkey, as the API reports it + contractRaw xdr.ContractId // the same id, as invocations need it + ledgers []uint32 // one per increment, ascending + txHashes []string // one per increment, same order +} + +func (f *eventsFixture) first() uint32 { return f.ledgers[0] } + +func (f *eventsFixture) increment(test *infrastructure.Test) { + resp := test.InvokeHostFunc(f.contractRaw, "increment") + f.ledgers = append(f.ledgers, resp.Ledger) + f.txHashes = append(f.txHashes, resp.TransactionHash) +} +func (f *eventsFixture) last() uint32 { return f.ledgers[len(f.ledgers)-1] } + +// The fixture deploys with CreateContractV2 and counts unified transaction +// events, both protocol 23 and later. +func newEventsTest(t *testing.T) *infrastructure.Test { + if infrastructure.GetCoreMaxSupportedProtocol() < 23 { + t.Skip("Only test this for protocol >= 23") + } + return infrastructure.NewTest(t, nil) +} + +// Each call waits for inclusion, so the increments land in distinct ledgers. +func deployAndIncrement(t *testing.T, test *infrastructure.Test, n int) *eventsFixture { + _, rawID, _ := test.CreateEventsContract() + fx := &eventsFixture{ + contractID: strkey.MustEncode(strkey.VersionByteContract, rawID[:]), + contractRaw: xdr.ContractId(rawID), + } + + for range n { + fx.increment(test) + } + for i := 1; i < n; i++ { + require.Greater(t, fx.ledgers[i], fx.ledgers[i-1], "increments must land in distinct ledgers") + } + return fx +} + +func drain(t *testing.T, rpc *client.Client, req protocol.GetEventsV2Request, +) ([]protocol.EventInfoV2, protocol.GetEventsV2Response) { + const maxPages = 200 + descending := req.Order == protocol.OrderDescending + var all []protocol.EventInfoV2 + for page := range maxPages { + resp, err := rpc.GetEventsV2(t.Context(), req) + require.NoError(t, err, "page %d", page) + if req.Limit != nil { + require.LessOrEqual(t, len(resp.Events), int(*req.Limit), "page %d", page) + } + for _, e := range resp.Events { + if len(all) > 0 { + prev := all[len(all)-1].Ledger + if descending { + require.LessOrEqual(t, e.Ledger, prev, "page %d: descending order broken", page) + } else { + require.GreaterOrEqual(t, e.Ledger, prev, "page %d: ascending order broken", page) + } + } + all = append(all, e) + } + if resp.ScanStatus != protocol.ScanStatusHasMore { + return all, resp + } + require.NotEmpty(t, resp.Cursor, "page %d: HAS_MORE without a cursor", page) + req = protocol.GetEventsV2Request{Cursor: resp.Cursor, Limit: req.Limit, Format: req.Format} + } + t.Fatalf("still HAS_MORE after %d pages", maxPages) + return nil, protocol.GetEventsV2Response{} +} + +// The fee and refund events belong to the native asset contract, so only the +// contract id tells the fixture's own event apart. +func requireFixtureEvents(t *testing.T, fx *eventsFixture, events []protocol.EventInfoV2) { + require.Len(t, events, eventsPerInvoke*len(fx.ledgers)) + ids := make(map[string]struct{}, len(events)) + fromContract := 0 + for _, e := range events { + _, dup := ids[e.ID] + require.False(t, dup, "duplicate event id %s", e.ID) + ids[e.ID] = struct{}{} + assert.Contains(t, fx.ledgers, uint32(e.Ledger)) + assert.Contains(t, fx.txHashes, e.TransactionHash) + assert.Equal(t, protocol.EventTypeContract, e.EventType) + assert.NotEmpty(t, e.ContractID) + if e.ContractID == fx.contractID { + fromContract++ + } + } + require.Equal(t, len(fx.ledgers), fromContract, "one event per increment from the fixture's contract") +} + +func eventIDs(events []protocol.EventInfoV2) []string { + ids := make([]string, 0, len(events)) + for _, e := range events { + ids = append(ids, e.ID) + } + return ids +} + +func reversed(s []string) []string { + out := make([]string, len(s)) + for i, v := range s { + out[len(s)-1-i] = v + } + return out +} + +func TestGetEventsV2AscendingDrainToTheTip(t *testing.T) { + test := newEventsTest(t) + rpc := test.GetRPCLient() + fx := deployAndIncrement(t, test, 3) + + events, last := drain(t, rpc, protocol.GetEventsV2Request{ + MinLedger: fx.first(), + Limit: new(uint(1)), + }) + requireFixtureEvents(t, fx, events) + for i := 1; i < len(events); i++ { + assert.LessOrEqual(t, events[i-1].Ledger, events[i].Ledger, "ascending order") + } + + assert.Equal(t, protocol.ScanStatusWaitingForLedgers, last.ScanStatus) + assert.NotEmpty(t, last.Cursor) + assert.GreaterOrEqual(t, last.LatestLedger, fx.last()) + assert.LessOrEqual(t, last.OldestLedger, fx.first()) + + again, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{Cursor: last.Cursor}) + require.NoError(t, err) + assert.Empty(t, again.Events) +} + +func TestGetEventsV2ClosedRangeBothOrders(t *testing.T) { + test := newEventsTest(t) + rpc := test.GetRPCLient() + fx := deployAndIncrement(t, test, 3) + + asc, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{ + MinLedger: fx.first(), + MaxLedger: fx.last(), + }) + require.NoError(t, err) + requireFixtureEvents(t, fx, asc.Events) + assert.Equal(t, protocol.ScanStatusComplete, asc.ScanStatus) + assert.Empty(t, asc.Cursor, "a complete query carries no cursor") + + desc, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{ + MinLedger: fx.first(), + MaxLedger: fx.last(), + Order: protocol.OrderDescending, + }) + require.NoError(t, err) + assert.Equal(t, protocol.ScanStatusComplete, desc.ScanStatus) + assert.Empty(t, desc.Cursor) + assert.Equal(t, reversed(eventIDs(asc.Events)), eventIDs(desc.Events), + "descending is the exact reverse of ascending") + + paged, last := drain(t, rpc, protocol.GetEventsV2Request{ + MinLedger: fx.first(), + MaxLedger: fx.last(), + Order: protocol.OrderDescending, + Limit: new(uint(2)), + }) + assert.Equal(t, protocol.ScanStatusComplete, last.ScanStatus) + assert.Equal(t, eventIDs(desc.Events), eventIDs(paged)) +} + +func TestGetEventsV2FiltersOverTheWire(t *testing.T) { + test := newEventsTest(t) + rpc := test.GetRPCLient() + fx := deployAndIncrement(t, test, 2) + + rng := protocol.GetEventsV2Request{MinLedger: fx.first(), MaxLedger: fx.last()} + call := func(t *testing.T, req protocol.GetEventsV2Request) protocol.GetEventsV2Response { + resp, err := rpc.GetEventsV2(t.Context(), req) + require.NoError(t, err) + assert.Equal(t, protocol.ScanStatusComplete, resp.ScanStatus) + return resp + } + contractOnly := func(resp protocol.GetEventsV2Response) []protocol.EventInfoV2 { + var out []protocol.EventInfoV2 + for _, e := range resp.Events { + if e.ContractID == fx.contractID { + out = append(out, e) + } + } + return out + } + + unfiltered := call(t, rng) + requireFixtureEvents(t, fx, unfiltered.Events) + sample := contractOnly(unfiltered)[0] + require.NotEmpty(t, sample.TopicXDR) + require.Empty(t, sample.TopicJSON, "base64 output carries no JSON topics") + topic0XDR := sample.TopicXDR[0] + + rngJSON := rng + rngJSON.Format = protocol.FormatJSON + asJSON := call(t, rngJSON) + sampleJSON := contractOnly(asJSON)[0] + require.Equal(t, sample.ID, sampleJSON.ID) + require.NotEmpty(t, sampleJSON.TopicJSON) + require.Empty(t, sampleJSON.TopicXDR, "JSON output carries no base64 topics") + topic0JSON := sampleJSON.TopicJSON[0] + + wantIDs := eventIDs(contractOnly(unfiltered)) + require.Len(t, wantIDs, len(fx.ledgers)) + + t.Run("contract id", func(t *testing.T) { + req := rng + req.Filters = []protocol.EventFilterV2{{ContractID: fx.contractID}} + assert.Equal(t, wantIDs, eventIDs(call(t, req).Events)) + }) + + t.Run("contract id and type", func(t *testing.T) { + req := rng + req.Filters = []protocol.EventFilterV2{{ContractID: fx.contractID, EventType: protocol.EventTypeContract}} + assert.Equal(t, wantIDs, eventIDs(call(t, req).Events)) + }) + + t.Run("topic0 as base64", func(t *testing.T) { + req := rng + req.Filters = []protocol.EventFilterV2{{ + ContractID: fx.contractID, + Topic0: json.RawMessage(`"` + topic0XDR + `"`), + }} + assert.Equal(t, wantIDs, eventIDs(call(t, req).Events)) + }) + + // The spec lists json as an xdrInputFormat; this server does not serve it. + t.Run("topic0 as JSON is rejected", func(t *testing.T) { + req := rng + req.XDRInputFormat = protocol.FormatJSON + req.Filters = []protocol.EventFilterV2{{ContractID: fx.contractID, Topic0: topic0JSON}} + _, err := rpc.GetEventsV2(t.Context(), req) + var rpcErr *jrpc2.Error + require.ErrorAs(t, err, &rpcErr) + assert.Equal(t, jrpc2.InvalidParams, rpcErr.Code) + var data protocol.InvalidParamsErrorData + require.NoError(t, json.Unmarshal(rpcErr.Data, &data)) + assert.Equal(t, protocol.ErrorReasonInvalidParams, data.Reason) + }) + + t.Run("topic0 that matches nothing", func(t *testing.T) { + nothing, err := xdr.MarshalBase64(xdr.ScVal{Type: xdr.ScValTypeScvSymbol, Sym: new(xdr.ScSymbol)}) + require.NoError(t, err) + req := rng + req.Filters = []protocol.EventFilterV2{{ + ContractID: fx.contractID, + Topic0: json.RawMessage(`"` + nothing + `"`), + }} + assert.Empty(t, call(t, req).Events) + }) + + t.Run("filters are OR-ed", func(t *testing.T) { + req := rng + req.Filters = []protocol.EventFilterV2{ + {ContractID: fx.contractID}, + {EventType: protocol.EventTypeContract}, + } + assert.Equal(t, eventIDs(unfiltered.Events), eventIDs(call(t, req).Events)) + }) +} + +// Same node, real Core output. The unit parity harness uses synthetic data. +func TestGetEventsV2MatchesV1(t *testing.T) { + test := newEventsTest(t) + rpc := test.GetRPCLient() + fx := deployAndIncrement(t, test, 3) + + for _, format := range []string{"", protocol.FormatJSON} { + t.Run("format="+format, func(t *testing.T) { + v1, err := rpc.GetEvents(t.Context(), protocol.GetEventsRequest{ + StartLedger: fx.first(), + EndLedger: fx.last() + 1, // v1 endLedger is exclusive + Pagination: &protocol.PaginationOptions{Limit: 1000}, + Format: format, + }) + require.NoError(t, err) + v2, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{ + MinLedger: fx.first(), + MaxLedger: fx.last(), + Format: format, + }) + require.NoError(t, err) + requireFixtureEvents(t, fx, v2.Events) + + require.Len(t, v2.Events, len(v1.Events)) + for i := range v1.Events { + assert.Equal(t, protocol.EventInfoV2(v1.Events[i]), v2.Events[i], "event %d", i) + } + // The tip moves between the two reads; v2 was read second. + assert.GreaterOrEqual(t, v2.LatestLedger, v1.LatestLedger, "latestLedger") + assert.GreaterOrEqual(t, v1.LatestLedger, fx.last(), "latestLedger") + assert.Equal(t, v1.OldestLedger, v2.OldestLedger, "oldestLedger") + }) + } +} + +// The union of the pages must equal a closed range read: nothing lost at the +// tip, nothing twice. +func TestGetEventsV2PagingWhileTipMoves(t *testing.T) { + test := newEventsTest(t) + rpc := test.GetRPCLient() + fx := deployAndIncrement(t, test, 1) + + seen := make([]protocol.EventInfoV2, 0, 16) + req := protocol.GetEventsV2Request{MinLedger: fx.first(), Limit: new(uint(1))} + const rounds = 3 + for round := range rounds { + got, last := drain(t, rpc, req) + seen = append(seen, got...) + require.Equal(t, protocol.ScanStatusWaitingForLedgers, last.ScanStatus, "round %d", round) + require.NotEmpty(t, last.Cursor, "round %d", round) + + idle, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{Cursor: last.Cursor}) + require.NoError(t, err) + require.Empty(t, idle.Events, "round %d: idle tip cursor served events", round) + require.NotEmpty(t, idle.Cursor, "round %d: idle tip cursor lost the cursor", round) + + if round < rounds-1 { + fx.increment(test) + } + req = protocol.GetEventsV2Request{Cursor: idle.Cursor, Limit: new(uint(1))} + } + + requireFixtureEvents(t, fx, seen) + final, err := rpc.GetEventsV2(t.Context(), protocol.GetEventsV2Request{MinLedger: fx.first(), MaxLedger: fx.last()}) + require.NoError(t, err) + assert.Equal(t, eventIDs(final.Events), eventIDs(seen), "paging to the tip disagrees with a closed range read") +} diff --git a/cmd/stellar-rpc/internal/rpcv2/integrationtest/main_test.go b/cmd/stellar-rpc/internal/rpcv2/integrationtest/main_test.go new file mode 100644 index 000000000..eafae0b29 --- /dev/null +++ b/cmd/stellar-rpc/internal/rpcv2/integrationtest/main_test.go @@ -0,0 +1,25 @@ +package integrationtest + +import ( + "fmt" + "os" + "testing" +) + +// Every test in this package needs the rpcv2 daemon. Default the harness +// selector to it, and refuse to run against anything else. +func TestMain(m *testing.M) { + const envVar = "STELLAR_RPC_INTEGRATION_TESTS_DAEMON" + switch os.Getenv(envVar) { + case "": + if err := os.Setenv(envVar, "rpcv2"); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + case "rpcv2": + default: + fmt.Fprintf(os.Stderr, "%s=%q: this package only runs against rpcv2\n", envVar, os.Getenv(envVar)) + os.Exit(1) + } + os.Exit(m.Run()) +} diff --git a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go index 03f476c9c..6b8bdb723 100644 --- a/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go +++ b/cmd/stellar-rpc/internal/rpcv2/jsonrpc_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "net/http/httptest" + "strings" "testing" "time" @@ -132,6 +133,59 @@ func TestJSONRPCHandler_GetEventsV2ReportsTypedErrorData(t *testing.T) { assert.Equal(t, protocol.ErrorReasonInvalidParams, data.Reason) } +// A cursor that does not decode gets a typed cursor_malformed error and the +// server keeps serving. The decoder itself is fuzzed in the query package. +func TestJSONRPCHandler_GetEventsV2RejectsMalformedCursors(t *testing.T) { + url := newTestRPCServer(t, seedServingRegistry(t)) + + first := rpcv2test.PostRPC(t, url, protocol.GetEventsV2MethodName, `{"minLedger":2,"limit":1}`) + require.Nil(t, first.Error) + var page struct { + Cursor string `json:"cursor"` + } + require.NoError(t, json.Unmarshal(first.Result, &page)) + require.NotEmpty(t, page.Cursor, "an open range at the tip hands back a cursor") + c := page.Cursor + prefixEnd := strings.IndexByte(c, '_') + 1 + require.Positive(t, prefixEnd, "cursor %q has no prefix separator", c) + prefix, body := c[:prefixEnd], c[prefixEnd:] + + flipped := "A" + if strings.HasSuffix(body, "A") { + flipped = "B" + } + for name, cursor := range map[string]string{ + "prefix only": prefix, + "body only": body, + "truncated body": prefix + body[:len(body)/2], + "flipped last char": prefix + body[:len(body)-1] + flipped, + "doubled body": prefix + body + body, + "v1 shaped cursor": "0000000137438953472-0000000000", + "wrong prefix": "gec0_" + body, + "garbage": prefix + "!!!not-base64!!!", + "extra trailing char": c + "A", + "whitespace inside": prefix + " " + body, + "very long": prefix + strings.Repeat("A", 4096), + } { + t.Run(name, func(t *testing.T) { + params, err := json.Marshal(map[string]string{"cursor": cursor}) + require.NoError(t, err) + out := rpcv2test.PostRPC(t, url, protocol.GetEventsV2MethodName, string(params)) + require.NotNil(t, out.Error) + assert.EqualValues(t, jrpc2.InvalidParams, out.Error.Code, "message: %s", out.Error.Message) + var data struct { + Reason string `json:"reason"` + } + require.NoError(t, json.Unmarshal(out.Error.Data, &data)) + assert.Equal(t, protocol.ErrorReasonCursorMalformed, data.Reason, + "message: %s", out.Error.Message) + }) + } + + again := rpcv2test.PostRPC(t, url, protocol.GetEventsV2MethodName, `{"minLedger":2,"limit":1}`) + require.Nil(t, again.Error, "the server must still serve after a burst of malformed cursors") +} + // Every field is optional, so a typo would widen the query rather than fail. func TestJSONRPCHandler_GetEventsV2RejectsUnknownFields(t *testing.T) { url := newTestRPCServer(t, seedServingRegistry(t)) diff --git a/go.mod b/go.mod index b1cd757dc..aa066f5ba 100644 --- a/go.mod +++ b/go.mod @@ -26,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.20260903222904-d8c8acf5f56c + github.com/stellar/go-stellar-sdk v0.7.4-0.20260918190431-7092f4b101ef github.com/stellar/streamhash v0.1.1-0.20260826155856-77556799545b github.com/stretchr/testify v1.12.1 ) @@ -125,7 +125,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/errors v0.9.1 - github.com/prometheus/client_model v0.6.2 + github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.2 // indirect github.com/prometheus/procfs v0.19.2 github.com/sagikazarmark/locafero v0.3.0 // indirect diff --git a/go.sum b/go.sum index 2828a53be..76139f86e 100644 --- a/go.sum +++ b/go.sum @@ -347,8 +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/go-stellar-sdk v0.7.4-0.20260903222904-d8c8acf5f56c h1:NxUyq0Xl4ASNoYllZFdUKIgSpWaNcnS0/iqzL1fo0JY= -github.com/stellar/go-stellar-sdk v0.7.4-0.20260903222904-d8c8acf5f56c/go.mod h1:bcErD+KvYtyrGqjaSjQeT7raOmlENi0WaORRwIJmfFE= +github.com/stellar/go-stellar-sdk v0.7.4-0.20260918190431-7092f4b101ef h1:vR1EoiV0gUXSAhi1Ouv//m3qR4Mg/WJvGLyjXT3VpoA= +github.com/stellar/go-stellar-sdk v0.7.4-0.20260918190431-7092f4b101ef/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.20260826155856-77556799545b h1:+qwFEBRESdIqVbLLMUtKf6RTnh7MDbNbyoP6T2WPfJc=