diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 71e57e11..2e458a80 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -37,7 +37,7 @@ jobs: # resolution options, using npm release or a gh ref: # # option #1, set the version of stellar-sdk based on a npm release version - SYSTEM_TEST_JS_STELLAR_SDK_NPM_VERSION: 11.1.0 + SYSTEM_TEST_JS_STELLAR_SDK_NPM_VERSION: 11.3.0 # option #2, set the version of stellar-sdk used as a ref to a gh repo if # a value is set on SYSTEM_TEST_JS_STELLAR_SDK_GH_REPO, it takes # precedence over any SYSTEM_TEST_JS_STELLAR_SDK_NPM_VERSION diff --git a/cmd/soroban-rpc/internal/jsonrpc.go b/cmd/soroban-rpc/internal/jsonrpc.go index b0ceb372..59d83420 100644 --- a/cmd/soroban-rpc/internal/jsonrpc.go +++ b/cmd/soroban-rpc/internal/jsonrpc.go @@ -208,7 +208,7 @@ func NewJSONRPCHandler(cfg *config.Config, params HandlerParams) Handler { }, { methodName: "simulateTransaction", - underlyingHandler: methods.NewSimulateTransactionHandler(params.Logger, params.LedgerEntryReader, params.LedgerReader, params.PreflightGetter), + underlyingHandler: methods.NewSimulateTransactionHandler(params.Logger, params.LedgerEntryReader, params.LedgerReader, params.Daemon, params.PreflightGetter), longName: "simulate_transaction", queueLimit: cfg.RequestBacklogSimulateTransactionQueueLimit, requestDurationLimit: cfg.MaxSimulateTransactionExecutionDuration, diff --git a/cmd/soroban-rpc/internal/methods/get_events.go b/cmd/soroban-rpc/internal/methods/get_events.go index dda0fa06..8b4a9439 100644 --- a/cmd/soroban-rpc/internal/methods/get_events.go +++ b/cmd/soroban-rpc/internal/methods/get_events.go @@ -8,7 +8,6 @@ import ( "time" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/go/strkey" "github.com/stellar/go/support/errors" @@ -429,7 +428,7 @@ func NewGetEventsHandler(eventsStore *events.MemoryStore, maxLimit, defaultLimit maxLimit: maxLimit, defaultLimit: defaultLimit, } - return handler.New(func(ctx context.Context, request GetEventsRequest) (GetEventsResponse, error) { + return NewHandler(func(ctx context.Context, request GetEventsRequest) (GetEventsResponse, error) { return eventsHandler.getEvents(request) }) } diff --git a/cmd/soroban-rpc/internal/methods/get_latest_ledger.go b/cmd/soroban-rpc/internal/methods/get_latest_ledger.go index 2f933304..3617e70d 100644 --- a/cmd/soroban-rpc/internal/methods/get_latest_ledger.go +++ b/cmd/soroban-rpc/internal/methods/get_latest_ledger.go @@ -4,7 +4,6 @@ import ( "context" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" ) @@ -20,7 +19,7 @@ type GetLatestLedgerResponse struct { // NewGetLatestLedgerHandler returns a JSON RPC handler to retrieve the latest ledger entry from Stellar core. func NewGetLatestLedgerHandler(ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader) jrpc2.Handler { - return handler.New(func(ctx context.Context) (GetLatestLedgerResponse, error) { + return NewHandler(func(ctx context.Context) (GetLatestLedgerResponse, error) { tx, err := ledgerEntryReader.NewTx(ctx) if err != nil { return GetLatestLedgerResponse{}, &jrpc2.Error{ diff --git a/cmd/soroban-rpc/internal/methods/get_ledger_entries.go b/cmd/soroban-rpc/internal/methods/get_ledger_entries.go index e9e58868..c3f2b617 100644 --- a/cmd/soroban-rpc/internal/methods/get_ledger_entries.go +++ b/cmd/soroban-rpc/internal/methods/get_ledger_entries.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/go/support/log" "github.com/stellar/go/xdr" @@ -40,7 +39,7 @@ const getLedgerEntriesMaxKeys = 200 // NewGetLedgerEntriesHandler returns a JSON RPC handler to retrieve the specified ledger entries from Stellar Core. func NewGetLedgerEntriesHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader) jrpc2.Handler { - return handler.New(func(ctx context.Context, request GetLedgerEntriesRequest) (GetLedgerEntriesResponse, error) { + return NewHandler(func(ctx context.Context, request GetLedgerEntriesRequest) (GetLedgerEntriesResponse, error) { if len(request.Keys) > getLedgerEntriesMaxKeys { return GetLedgerEntriesResponse{}, &jrpc2.Error{ Code: jrpc2.InvalidParams, diff --git a/cmd/soroban-rpc/internal/methods/get_ledger_entry.go b/cmd/soroban-rpc/internal/methods/get_ledger_entry.go index e457a3b1..910d2155 100644 --- a/cmd/soroban-rpc/internal/methods/get_ledger_entry.go +++ b/cmd/soroban-rpc/internal/methods/get_ledger_entry.go @@ -5,8 +5,6 @@ import ( "fmt" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" - "github.com/stellar/go/support/log" "github.com/stellar/go/xdr" @@ -33,7 +31,7 @@ type GetLedgerEntryResponse struct { // Deprecated. use NewGetLedgerEntriesHandler instead. // TODO(https://github.com/stellar/soroban-tools/issues/374) remove after getLedgerEntries is deployed. func NewGetLedgerEntryHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader) jrpc2.Handler { - return handler.New(func(ctx context.Context, request GetLedgerEntryRequest) (GetLedgerEntryResponse, error) { + return NewHandler(func(ctx context.Context, request GetLedgerEntryRequest) (GetLedgerEntryResponse, error) { var key xdr.LedgerKey if err := xdr.SafeUnmarshalBase64(request.Key, &key); err != nil { logger.WithError(err).WithField("request", request). diff --git a/cmd/soroban-rpc/internal/methods/get_network.go b/cmd/soroban-rpc/internal/methods/get_network.go index 5c990c41..f706e7c6 100644 --- a/cmd/soroban-rpc/internal/methods/get_network.go +++ b/cmd/soroban-rpc/internal/methods/get_network.go @@ -4,7 +4,6 @@ import ( "context" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces" ) @@ -20,7 +19,7 @@ type GetNetworkResponse struct { // NewGetNetworkHandler returns a json rpc handler to for the getNetwork method func NewGetNetworkHandler(daemon interfaces.Daemon, networkPassphrase, friendbotURL string) jrpc2.Handler { coreClient := daemon.CoreClient() - return handler.New(func(ctx context.Context, request GetNetworkRequest) (GetNetworkResponse, error) { + return NewHandler(func(ctx context.Context, request GetNetworkRequest) (GetNetworkResponse, error) { info, err := coreClient.Info(ctx) if err != nil { return GetNetworkResponse{}, (&jrpc2.Error{ diff --git a/cmd/soroban-rpc/internal/methods/get_transaction.go b/cmd/soroban-rpc/internal/methods/get_transaction.go index 1e6ef610..166cb152 100644 --- a/cmd/soroban-rpc/internal/methods/get_transaction.go +++ b/cmd/soroban-rpc/internal/methods/get_transaction.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/go/xdr" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow" @@ -121,7 +120,7 @@ func GetTransaction(getter transactionGetter, request GetTransactionRequest) (Ge // NewGetTransactionHandler returns a get transaction json rpc handler func NewGetTransactionHandler(getter transactionGetter) jrpc2.Handler { - return handler.New(func(ctx context.Context, request GetTransactionRequest) (GetTransactionResponse, error) { + return NewHandler(func(ctx context.Context, request GetTransactionRequest) (GetTransactionResponse, error) { return GetTransaction(getter, request) }) } diff --git a/cmd/soroban-rpc/internal/methods/handler.go b/cmd/soroban-rpc/internal/methods/handler.go new file mode 100644 index 00000000..2dc4d99b --- /dev/null +++ b/cmd/soroban-rpc/internal/methods/handler.go @@ -0,0 +1,17 @@ +package methods + +import ( + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" +) + +func NewHandler(fn any) jrpc2.Handler { + fi, err := handler.Check(fn) + if err != nil { + panic(err) + } + // explicitly disable array arguments since otherwise we cannot add + // new method arguments without breaking backwards compatibility with clients + fi.AllowArray(false) + return fi.Wrap() +} diff --git a/cmd/soroban-rpc/internal/methods/handler_test.go b/cmd/soroban-rpc/internal/methods/handler_test.go new file mode 100644 index 00000000..564f7f51 --- /dev/null +++ b/cmd/soroban-rpc/internal/methods/handler_test.go @@ -0,0 +1,60 @@ +package methods + +import ( + "context" + "testing" + + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" + "github.com/stretchr/testify/assert" +) + +type Request struct { + Parameter string `json:"parameter"` +} + +func TestNewHandlerNoArrayParameters(t *testing.T) { + callCount := 0 + f := func(ctx context.Context, request Request) error { + callCount++ + assert.Equal(t, "bar", request.Parameter) + return nil + } + objectRequest := `{ +"jsonrpc": "2.0", +"id": 1, +"method": "foo", +"params": { "parameter": "bar" } +}` + requests, err := jrpc2.ParseRequests([]byte(objectRequest)) + assert.NoError(t, err) + assert.Len(t, requests, 1) + finalObjectRequest := requests[0].ToRequest() + + // object parameters should work with our handlers + customHandler := NewHandler(f) + _, err = customHandler(context.Background(), finalObjectRequest) + assert.NoError(t, err) + assert.Equal(t, 1, callCount) + + arrayRequest := `{ +"jsonrpc": "2.0", +"id": 1, +"method": "foo", +"params": ["bar"] +}` + requests, err = jrpc2.ParseRequests([]byte(arrayRequest)) + assert.NoError(t, err) + assert.Len(t, requests, 1) + finalArrayRequest := requests[0].ToRequest() + + // Array requests should work with the normal handler, but not with our handlers + stdHandler := handler.New(f) + _, err = stdHandler(context.Background(), finalArrayRequest) + assert.NoError(t, err) + assert.Equal(t, 2, callCount) + + _, err = customHandler(context.Background(), finalArrayRequest) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid parameters") +} diff --git a/cmd/soroban-rpc/internal/methods/health.go b/cmd/soroban-rpc/internal/methods/health.go index b8f684af..f9b1c50e 100644 --- a/cmd/soroban-rpc/internal/methods/health.go +++ b/cmd/soroban-rpc/internal/methods/health.go @@ -6,7 +6,6 @@ import ( "time" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/ledgerbucketwindow" ) @@ -24,7 +23,7 @@ type LedgerRangeGetter interface { // NewHealthCheck returns a health check json rpc handler func NewHealthCheck(retentionWindow uint32, ledgerRangeGetter LedgerRangeGetter, maxHealthyLedgerLatency time.Duration) jrpc2.Handler { - return handler.New(func(ctx context.Context) (HealthCheckResult, error) { + return NewHandler(func(ctx context.Context) (HealthCheckResult, error) { ledgerRange := ledgerRangeGetter.GetLedgerRange() if ledgerRange.LastLedger.Sequence < 1 { return HealthCheckResult{}, jrpc2.Error{ diff --git a/cmd/soroban-rpc/internal/methods/send_transaction.go b/cmd/soroban-rpc/internal/methods/send_transaction.go index 215e2998..82d014d1 100644 --- a/cmd/soroban-rpc/internal/methods/send_transaction.go +++ b/cmd/soroban-rpc/internal/methods/send_transaction.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/support/log" @@ -47,7 +46,7 @@ type SendTransactionRequest struct { // NewSendTransactionHandler returns a submit transaction json rpc handler func NewSendTransactionHandler(daemon interfaces.Daemon, logger *log.Entry, ledgerRangeGetter LedgerRangeGetter, passphrase string) jrpc2.Handler { submitter := daemon.CoreClient() - return handler.New(func(ctx context.Context, request SendTransactionRequest) (SendTransactionResponse, error) { + return NewHandler(func(ctx context.Context, request SendTransactionRequest) (SendTransactionResponse, error) { var envelope xdr.TransactionEnvelope err := xdr.SafeUnmarshalBase64(request.Transaction, &envelope) if err != nil { diff --git a/cmd/soroban-rpc/internal/methods/simulate_transaction.go b/cmd/soroban-rpc/internal/methods/simulate_transaction.go index 6ad3c63b..9ad98d80 100644 --- a/cmd/soroban-rpc/internal/methods/simulate_transaction.go +++ b/cmd/soroban-rpc/internal/methods/simulate_transaction.go @@ -9,10 +9,10 @@ import ( "strings" "github.com/creachadair/jrpc2" - "github.com/creachadair/jrpc2/handler" "github.com/stellar/go/support/log" "github.com/stellar/go/xdr" + "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/daemon/interfaces" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/db" "github.com/stellar/soroban-rpc/cmd/soroban-rpc/internal/preflight" ) @@ -157,9 +157,9 @@ type PreflightGetter interface { } // NewSimulateTransactionHandler returns a json rpc handler to run preflight simulations -func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader, getter PreflightGetter) jrpc2.Handler { +func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.LedgerEntryReader, ledgerReader db.LedgerReader, daemon interfaces.Daemon, getter PreflightGetter) jrpc2.Handler { - return handler.New(func(ctx context.Context, request SimulateTransactionRequest) SimulateTransactionResponse { + return NewHandler(func(ctx context.Context, request SimulateTransactionRequest) SimulateTransactionResponse { var txEnvelope xdr.TransactionEnvelope if err := xdr.SafeUnmarshalBase64(request.Transaction, &txEnvelope); err != nil { logger.WithError(err).WithField("request", request). @@ -213,7 +213,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge Error: err.Error(), } } - bucketListSize, err := getBucketListSize(ctx, ledgerReader, latestLedger) + bucketListSize, protocolVersion, err := getBucketListSizeAndProtocolVersion(ctx, ledgerReader, latestLedger) if err != nil { return SimulateTransactionResponse{ Error: err.Error(), @@ -231,6 +231,7 @@ func NewSimulateTransactionHandler(logger *log.Entry, ledgerEntryReader db.Ledge OperationBody: op.Body, Footprint: footprint, ResourceConfig: resource_config, + ProtocolVersion: protocolVersion, } result, err := getter.GetPreflight(ctx, params) if err != nil { @@ -285,17 +286,17 @@ func base64EncodeSlice(in [][]byte) []string { return result } -func getBucketListSize(ctx context.Context, ledgerReader db.LedgerReader, latestLedger uint32) (uint64, error) { +func getBucketListSizeAndProtocolVersion(ctx context.Context, ledgerReader db.LedgerReader, latestLedger uint32) (uint64, uint32, error) { // obtain bucket size var closeMeta, ok, err = ledgerReader.GetLedger(ctx, latestLedger) if err != nil { - return 0, err + return 0, 0, err } if !ok { - return 0, fmt.Errorf("missing meta for latest ledger (%d)", latestLedger) + return 0, 0, fmt.Errorf("missing meta for latest ledger (%d)", latestLedger) } if closeMeta.V != 1 { - return 0, fmt.Errorf("latest ledger (%d) meta has unexpected verion (%d)", latestLedger, closeMeta.V) + return 0, 0, fmt.Errorf("latest ledger (%d) meta has unexpected verion (%d)", latestLedger, closeMeta.V) } - return uint64(closeMeta.V1.TotalByteSizeOfBucketList), nil + return uint64(closeMeta.V1.TotalByteSizeOfBucketList), uint32(closeMeta.V1.LedgerHeader.Header.LedgerVersion), nil } diff --git a/cmd/soroban-rpc/internal/preflight/pool.go b/cmd/soroban-rpc/internal/preflight/pool.go index 4eeddb53..3ea704d8 100644 --- a/cmd/soroban-rpc/internal/preflight/pool.go +++ b/cmd/soroban-rpc/internal/preflight/pool.go @@ -156,6 +156,7 @@ func (pwp *PreflightWorkerPool) GetPreflight(ctx context.Context, params Preflig Footprint: params.Footprint, ResourceConfig: params.ResourceConfig, EnableDebug: pwp.enableDebug, + ProtocolVersion: params.ProtocolVersion, } resultC := make(chan workerResult) select { diff --git a/cmd/soroban-rpc/internal/preflight/preflight.go b/cmd/soroban-rpc/internal/preflight/preflight.go index de3c7f4e..37ddcc9b 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight.go +++ b/cmd/soroban-rpc/internal/preflight/preflight.go @@ -91,6 +91,7 @@ type PreflightGetterParameters struct { OperationBody xdr.OperationBody Footprint xdr.LedgerFootprint ResourceConfig ResourceConfig + ProtocolVersion uint32 } type PreflightParameters struct { @@ -103,6 +104,7 @@ type PreflightParameters struct { BucketListSize uint64 ResourceConfig ResourceConfig EnableDebug bool + ProtocolVersion uint32 } type XDRDiff struct { @@ -174,7 +176,7 @@ func getLedgerInfo(params PreflightParameters) (C.ledger_info_t, error) { li := C.ledger_info_t{ network_passphrase: C.CString(params.NetworkPassphrase), sequence_number: C.uint32_t(simulationLedgerSeq), - protocol_version: 21, + protocol_version: C.uint32_t(params.ProtocolVersion), timestamp: C.uint64_t(time.Now().Unix()), // Current base reserve is 0.5XLM (in stroops) base_reserve: 5_000_000, diff --git a/cmd/soroban-rpc/internal/preflight/preflight_test.go b/cmd/soroban-rpc/internal/preflight/preflight_test.go index 401912e4..1d4515b0 100644 --- a/cmd/soroban-rpc/internal/preflight/preflight_test.go +++ b/cmd/soroban-rpc/internal/preflight/preflight_test.go @@ -359,6 +359,7 @@ func getPreflightParameters(t testing.TB, dbConfig *preflightParametersDBConfig) NetworkPassphrase: "foo", LedgerEntryReadTx: ledgerEntryReadTx, BucketListSize: 200, + ProtocolVersion: 20, } return params }