From 475af3f0e1b2fabba88a34c014552ccfd343259d Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 22 Jan 2024 16:28:21 -0500 Subject: [PATCH 01/68] Add new txsub endpoint - 1 --- .../actions/submit_transaction_async.go | 189 ++++++++++++++++++ services/horizon/internal/httpx/router.go | 7 + 2 files changed, 196 insertions(+) create mode 100644 services/horizon/internal/actions/submit_transaction_async.go diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go new file mode 100644 index 0000000000..54cb41a933 --- /dev/null +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -0,0 +1,189 @@ +package actions + +import ( + "encoding/hex" + "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/network" + proto "github.com/stellar/go/protocols/stellarcore" + hProblem "github.com/stellar/go/services/horizon/internal/render/problem" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/support/render/problem" + "github.com/stellar/go/xdr" + "mime" + "net/http" +) + +type AsyncSubmitTransactionHandler struct { + NetworkPassphrase string + DisableTxSub bool + StellarCore *stellarcore.Client + CoreStateGetter +} + +// SendTransactionResponse represents the transaction submission response returned by Horizon +// when using the transaction-submission-v2 endpoint. +type SendTransactionResponse struct { + // ErrorResultXDR is present only if Status is equal to proto.TXStatusError. + // ErrorResultXDR is a TransactionResult xdr string which contains details on why + // the transaction could not be accepted by stellar-core. + ErrorResultXDR string `json:"errorResultXdr,omitempty"` + // DiagnosticEventsXDR is present only if Status is equal to proto.TXStatusError. + // DiagnosticEventsXDR is a base64-encoded slice of xdr.DiagnosticEvent + DiagnosticEventsXDR string `json:"diagnosticEventsXdr,omitempty"` + // Status represents the status of the transaction submission returned by stellar-core. + // Status can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, + // proto.TXStatusTryAgainLater, or proto.TXStatusError. + Status string `json:"status"` + // Hash is a hash of the transaction which can be used to look up whether + // the transaction was included in the ledger. + Hash string `json:"hash"` +} + +func (handler AsyncSubmitTransactionHandler) extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { + result := envelopeInfo{raw: raw} + err := xdr.SafeUnmarshalBase64(raw, &result.parsed) + if err != nil { + return result, err + } + + var hash [32]byte + hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) + if err != nil { + return result, err + } + result.hash = hex.EncodeToString(hash[:]) + if result.parsed.IsFeeBump() { + hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) + if err != nil { + return result, err + } + result.innerHash = hex.EncodeToString(hash[:]) + } + return result, nil +} + +func (handler AsyncSubmitTransactionHandler) validateBodyType(r *http.Request) error { + c := r.Header.Get("Content-Type") + if c == "" { + return nil + } + + mt, _, err := mime.ParseMediaType(c) + if err != nil { + return errors.Wrap(err, "Could not determine mime type") + } + + if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { + return &hProblem.UnsupportedMediaType + } + return nil +} + +func (handler AsyncSubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { + if err := handler.validateBodyType(r); err != nil { + return nil, err + } + + raw, err := getString(r, "tx") + if err != nil { + return nil, err + } + + if handler.DisableTxSub { + return nil, &problem.P{ + Type: "transaction_submission_disabled", + Title: "Transaction Submission Disabled", + Status: http.StatusMethodNotAllowed, + Detail: "Transaction submission has been disabled for Horizon. " + + "To enable it again, remove env variable DISABLE_TX_SUB.", + Extras: map[string]interface{}{ + "envelope_xdr": raw, + }, + } + } + + info, err := handler.extractEnvelopeInfo(raw, handler.NetworkPassphrase) + if err != nil { + return nil, &problem.P{ + Type: "transaction_malformed", + Title: "Transaction Malformed", + Status: http.StatusBadRequest, + Detail: "Horizon could not decode the transaction envelope in this " + + "request. A transaction should be an XDR TransactionEnvelope struct " + + "encoded using base64. The envelope read from this request is " + + "echoed in the `extras.envelope_xdr` field of this response for your " + + "convenience.", + Extras: map[string]interface{}{ + "envelope_xdr": raw, + }, + } + } + + coreState := handler.GetCoreState() + if !coreState.Synced { + return nil, hProblem.StaleHistory + } + + resp, err := handler.StellarCore.SubmitTransaction(r.Context(), info.raw) + if err != nil { + return nil, &problem.P{ + Type: "transaction_submission_failed", + Title: "Transaction Submission Failed", + Status: http.StatusBadRequest, + Detail: "Could not submit transaction to stellar-core. " + + "The `extras.error` field on this response contains further " + + "details. Descriptions of each code can be found at: " + + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_failed", + Extras: map[string]interface{}{ + "envelope_xdr": raw, + "error": err, + }, + } + } + + if resp.IsException() { + return nil, &problem.P{ + Type: "transaction_submission_exception", + Title: "Transaction Submission Exception", + Status: http.StatusBadRequest, + Detail: "Received exception from stellar-core." + + "The `extras.error` field on this response contains further " + + "details. Descriptions of each code can be found at: " + + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_exception", + Extras: map[string]interface{}{ + "envelope_xdr": raw, + "error": err, + }, + } + } + + switch resp.Status { + case proto.TXStatusError: + return SendTransactionResponse{ + ErrorResultXDR: resp.Error, + DiagnosticEventsXDR: resp.DiagnosticEvents, + Status: resp.Status, + Hash: info.hash, + }, nil + case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: + return SendTransactionResponse{ + Status: resp.Status, + Hash: info.hash, + }, nil + default: + return nil, &problem.P{ + Type: "transaction_submission_invalid_status", + Title: "Transaction Submission Invalid Status", + Status: http.StatusBadRequest, + Detail: "Received invalid status from stellar-core." + + "The `extras.error` field on this response contains further " + + "details. Descriptions of each code can be found at: " + + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_invalid_status", + Extras: map[string]interface{}{ + "envelope_xdr": raw, + "error": err, + }, + } + } + +} diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 8fa57d0379..9677a2b761 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -331,6 +331,13 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate CoreStateGetter: config.CoreGetter, }}) + // Async Transaction submission API + r.Method(http.MethodPost, "/v2/transactions", ObjectActionHandler{actions.AsyncSubmitTransactionHandler{ + NetworkPassphrase: config.NetworkPassphrase, + DisableTxSub: config.DisableTxSub, + CoreStateGetter: config.CoreGetter, + }}) + // Network state related endpoints r.Method(http.MethodGet, "/fee_stats", ObjectActionHandler{actions.FeeStatsHandler{}}) From d4555b359c554b14eef38408779d0f3eec869171 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 23 Jan 2024 11:44:08 -0500 Subject: [PATCH 02/68] Add new txsub endpoint - 2 --- .../horizon/internal/actions/submit_transaction_async.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 54cb41a933..3507f65506 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -79,7 +79,7 @@ func (handler AsyncSubmitTransactionHandler) validateBodyType(r *http.Request) e return nil } -func (handler AsyncSubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { +func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { if err := handler.validateBodyType(r); err != nil { return nil, err } @@ -152,7 +152,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(w HeaderWriter, r *http "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_exception", Extras: map[string]interface{}{ "envelope_xdr": raw, - "error": err, + "error": resp.Exception, }, } } @@ -181,7 +181,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(w HeaderWriter, r *http "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_invalid_status", Extras: map[string]interface{}{ "envelope_xdr": raw, - "error": err, + "error": resp.Error, }, } } From e883120b93b3e203c4b249519d53acc12295deb7 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 23 Jan 2024 14:29:02 -0500 Subject: [PATCH 03/68] Add new txsub endpoint - 3 --- .../horizon/internal/actions/submit_transaction_async.go | 4 ++-- services/horizon/internal/app.go | 1 + services/horizon/internal/httpx/router.go | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 3507f65506..a3434c54c0 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -16,7 +16,7 @@ import ( type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - StellarCore *stellarcore.Client + CoreClient *stellarcore.Client CoreStateGetter } @@ -124,7 +124,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, hProblem.StaleHistory } - resp, err := handler.StellarCore.SubmitTransaction(r.Context(), info.raw) + resp, err := handler.CoreClient.SubmitTransaction(r.Context(), info.raw) if err != nil { return nil, &problem.P{ Type: "transaction_submission_failed", diff --git a/services/horizon/internal/app.go b/services/horizon/internal/app.go index b1cd7a1c85..98c1ed408e 100644 --- a/services/horizon/internal/app.go +++ b/services/horizon/internal/app.go @@ -543,6 +543,7 @@ func (a *App) init() error { FriendbotURL: a.config.FriendbotURL, EnableIngestionFiltering: a.config.EnableIngestionFiltering, DisableTxSub: a.config.DisableTxSub, + StellarCoreURL: a.config.StellarCoreURL, HealthCheck: healthCheck{ session: a.historyQ.SessionInterface, ctx: a.ctx, diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 9677a2b761..58754217d3 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -3,6 +3,7 @@ package httpx import ( "compress/flate" "fmt" + "github.com/stellar/go/clients/stellarcore" "net/http" "net/http/pprof" "net/url" @@ -50,6 +51,7 @@ type RouterConfig struct { HealthCheck http.Handler EnableIngestionFiltering bool DisableTxSub bool + StellarCoreURL string } type Router struct { @@ -336,6 +338,10 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, + CoreClient: &stellarcore.Client{ + HTTP: http.DefaultClient, + URL: config.StellarCoreURL, + }, }}) // Network state related endpoints From 00e1a29d5e3f62160325371af736162c315af50c Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 29 Jan 2024 10:13:55 -0500 Subject: [PATCH 04/68] Update Status to TxStatus --- .../internal/actions/submit_transaction_async.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index a3434c54c0..00bee26145 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -30,10 +30,10 @@ type SendTransactionResponse struct { // DiagnosticEventsXDR is present only if Status is equal to proto.TXStatusError. // DiagnosticEventsXDR is a base64-encoded slice of xdr.DiagnosticEvent DiagnosticEventsXDR string `json:"diagnosticEventsXdr,omitempty"` - // Status represents the status of the transaction submission returned by stellar-core. - // Status can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, + // TxStatus represents the status of the transaction submission returned by stellar-core. + // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, // proto.TXStatusTryAgainLater, or proto.TXStatusError. - Status string `json:"status"` + TxStatus string `json:"status"` // Hash is a hash of the transaction which can be used to look up whether // the transaction was included in the ledger. Hash string `json:"hash"` @@ -162,13 +162,13 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return SendTransactionResponse{ ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, - Status: resp.Status, + TxStatus: resp.Status, Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: return SendTransactionResponse{ - Status: resp.Status, - Hash: info.hash, + TxStatus: resp.Status, + Hash: info.hash, }, nil default: return nil, &problem.P{ From 28a009e2771649b97e7092f46f02920c3e53af81 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 31 Jan 2024 15:00:36 -0500 Subject: [PATCH 05/68] Add unittests for new endpoint --- clients/stellarcore/client.go | 12 ++++ clients/stellarcore/mocks.go | 70 +++++++++++++++++++ .../actions/submit_transaction_async.go | 36 +++++++--- 3 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 clients/stellarcore/mocks.go diff --git a/clients/stellarcore/client.go b/clients/stellarcore/client.go index 931a076021..2936d099d8 100644 --- a/clients/stellarcore/client.go +++ b/clients/stellarcore/client.go @@ -29,6 +29,18 @@ type Client struct { URL string } +type ClientInterface interface { + Upgrade(ctx context.Context, version int) (err error) + GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) + Info(ctx context.Context) (resp *proto.InfoResponse, err error) + SetCursor(ctx context.Context, id string, cursor int32) (err error) + SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) + WaitForNetworkSync(ctx context.Context) error + ManualClose(ctx context.Context) (err error) + http() HTTP + simpleGet(ctx context.Context, newPath string, query url.Values) (*http.Request, error) +} + // drainReponse is a helper method for draining the body stream off the http // response object and optionally close the stream. It would also update the // error but only as long as there wasn't an error before - this would allow diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go new file mode 100644 index 0000000000..f103fda7dc --- /dev/null +++ b/clients/stellarcore/mocks.go @@ -0,0 +1,70 @@ +package stellarcore + +import ( + "context" + "github.com/stellar/go/support/http/httptest" + "net/http" + "net/url" + + proto "github.com/stellar/go/protocols/stellarcore" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/mock" +) + +// MockClient is a mockable stellar-core client. +type MockClient struct { + mock.Mock +} + +// Upgrade mocks the Upgrade method +func (m *MockClient) Upgrade(ctx context.Context, version int) error { + args := m.Called(ctx, version) + return args.Error(0) +} + +// GetLedgerEntry mocks the GetLedgerEntry method +func (m *MockClient) GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) { + args := m.Called(ctx, ledgerKey) + return args.Get(0).(proto.GetLedgerEntryResponse), args.Error(1) +} + +// Info mocks the Info method +func (m *MockClient) Info(ctx context.Context) (*proto.InfoResponse, error) { + args := m.Called(ctx) + return args.Get(0).(*proto.InfoResponse), args.Error(1) +} + +// SetCursor mocks the SetCursor method +func (m *MockClient) SetCursor(ctx context.Context, id string, cursor int32) error { + args := m.Called(ctx, id, cursor) + return args.Error(0) +} + +// SubmitTransaction mocks the SubmitTransaction method +func (m *MockClient) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { + args := m.Called(ctx, envelope) + return args.Get(0).(*proto.TXResponse), args.Error(1) +} + +// WaitForNetworkSync mocks the WaitForNetworkSync method +func (m *MockClient) WaitForNetworkSync(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +// ManualClose mocks the ManualClose method +func (m *MockClient) ManualClose(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +// Mock for http() method +func (m *MockClient) http() HTTP { + return httptest.NewClient() +} + +// Mock for simpleGet() method +func (m *MockClient) simpleGet(ctx context.Context, newPath string, query url.Values) (*http.Request, error) { + args := m.Called(ctx, newPath, query) + return args.Get(0).(*http.Request), args.Error(1) +} diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 00bee26145..d6f63638df 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -13,16 +13,23 @@ import ( "net/http" ) +const ( + HttpStatusCodeForPending = http.StatusCreated + HttpStatusCodeForDuplicate = http.StatusConflict + HttpStatusCodeForTryAgainLater = http.StatusServiceUnavailable + HttpStatusCodeForError = http.StatusBadRequest +) + type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - CoreClient *stellarcore.Client + CoreClient stellarcore.ClientInterface CoreStateGetter } -// SendTransactionResponse represents the transaction submission response returned by Horizon +// TransactionSubmissionResponse represents the response returned by Horizon // when using the transaction-submission-v2 endpoint. -type SendTransactionResponse struct { +type TransactionSubmissionResponse struct { // ErrorResultXDR is present only if Status is equal to proto.TXStatusError. // ErrorResultXDR is a TransactionResult xdr string which contains details on why // the transaction could not be accepted by stellar-core. @@ -33,7 +40,9 @@ type SendTransactionResponse struct { // TxStatus represents the status of the transaction submission returned by stellar-core. // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, // proto.TXStatusTryAgainLater, or proto.TXStatusError. - TxStatus string `json:"status"` + TxStatus string `json:"tx_status"` + // HttpStatus represents the corresponding http status code. + HttpStatus int `json:"status"` // Hash is a hash of the transaction which can be used to look up whether // the transaction was included in the ledger. Hash string `json:"hash"` @@ -159,16 +168,27 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http switch resp.Status { case proto.TXStatusError: - return SendTransactionResponse{ + return TransactionSubmissionResponse{ ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, TxStatus: resp.Status, + HttpStatus: HttpStatusCodeForError, Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: - return SendTransactionResponse{ - TxStatus: resp.Status, - Hash: info.hash, + var httpStatus int + if resp.Status == proto.TXStatusPending { + httpStatus = HttpStatusCodeForPending + } else if resp.Status == proto.TXStatusDuplicate { + httpStatus = HttpStatusCodeForDuplicate + } else { + httpStatus = HttpStatusCodeForTryAgainLater + } + + return TransactionSubmissionResponse{ + TxStatus: resp.Status, + HttpStatus: httpStatus, + Hash: info.hash, }, nil default: return nil, &problem.P{ From 2ed005427f6445afa8768322494608cf894487da Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 31 Jan 2024 15:05:14 -0500 Subject: [PATCH 06/68] Create submit_transaction_async_test.go --- .../actions/submit_transaction_async_test.go | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 services/horizon/internal/actions/submit_transaction_async_test.go diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go new file mode 100644 index 0000000000..1c5de7dbab --- /dev/null +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -0,0 +1,208 @@ +package actions + +import ( + "context" + "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/network" + proto "github.com/stellar/go/protocols/stellarcore" + "github.com/stellar/go/services/horizon/internal/corestate" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/support/render/problem" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +const ( + TxXDR = "AAAAAAGUcmKO5465JxTSLQOQljwk2SfqAJmZSG6JH6wtqpwhAAABLAAAAAAAAAABAAAAAAAAAAEAAAALaGVsbG8gd29ybGQAAAAAAwAAAAAAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAAAvrwgAAAAAAAAAAAQAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNgAAAAAN4Lazj4x61AAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLaqcIQAAAEBKwqWy3TaOxoGnfm9eUjfTRBvPf34dvDA0Nf+B8z4zBob90UXtuCqmQqwMCyH+okOI3c05br3khkH0yP4kCwcE" + TxHash = "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889" +) + +func createRequest() *http.Request { + form := url.Values{} + form.Set("tx", TxXDR) + + request, _ := http.NewRequest( + "POST", + "http://localhost:8000/v2/transactions", + strings.NewReader(form.Encode()), + ) + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return request +} + +func TestAsyncSubmitTransactionHandler_DisabledTxSub(t *testing.T) { + handler := AsyncSubmitTransactionHandler{ + DisableTxSub: true, + } + + request := createRequest() + w := httptest.NewRecorder() + + _, err := handler.GetResource(w, request) + assert.NotNil(t, err) + assert.IsType(t, &problem.P{}, err) + p := err.(*problem.P) + assert.Equal(t, "transaction_submission_disabled", p.Type) + assert.Equal(t, http.StatusMethodNotAllowed, p.Status) +} + +func TestAsyncSubmitTransactionHandler_MalformedTransaction(t *testing.T) { + handler := AsyncSubmitTransactionHandler{} + + request := createRequest() + w := httptest.NewRecorder() + + _, err := handler.GetResource(w, request) + assert.NotNil(t, err) + assert.IsType(t, &problem.P{}, err) + p := err.(*problem.P) + assert.Equal(t, "transaction_malformed", p.Type) + assert.Equal(t, http.StatusBadRequest, p.Status) +} + +func TestAsyncSubmitTransactionHandler_CoreNotSynced(t *testing.T) { + coreStateGetter := new(coreStateGetterMock) + coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: false}) + handler := AsyncSubmitTransactionHandler{ + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + } + + request := createRequest() + w := httptest.NewRecorder() + + _, err := handler.GetResource(w, request) + assert.NotNil(t, err) + assert.IsType(t, problem.P{}, err) + p := err.(problem.P) + assert.Equal(t, "stale_history", p.Type) + assert.Equal(t, http.StatusServiceUnavailable, p.Status) +} + +func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) { + coreStateGetter := new(coreStateGetterMock) + coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + + mockCoreClient := &stellarcore.MockClient{} + mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) + + handler := AsyncSubmitTransactionHandler{ + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClient: mockCoreClient, + } + + request := createRequest() + w := httptest.NewRecorder() + + _, err := handler.GetResource(w, request) + assert.NotNil(t, err) + assert.IsType(t, &problem.P{}, err) + p := err.(*problem.P) + assert.Equal(t, "transaction_submission_failed", p.Type) + assert.Equal(t, http.StatusBadRequest, p.Status) +} + +func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing.T) { + coreStateGetter := new(coreStateGetterMock) + coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + + mockCoreClient := &stellarcore.MockClient{} + mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ + Exception: "some-exception", + }, nil) + + handler := AsyncSubmitTransactionHandler{ + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClient: mockCoreClient, + } + + request := createRequest() + w := httptest.NewRecorder() + + _, err := handler.GetResource(w, request) + assert.NotNil(t, err) + assert.IsType(t, &problem.P{}, err) + p := err.(*problem.P) + assert.Equal(t, "transaction_submission_exception", p.Type) + assert.Equal(t, http.StatusBadRequest, p.Status) +} + +func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { + coreStateGetter := new(coreStateGetterMock) + coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + + successCases := []struct { + mockCoreResponse *proto.TXResponse + expectedResponse TransactionSubmissionResponse + }{ + { + mockCoreResponse: &proto.TXResponse{ + Exception: "", + Error: "test-error", + Status: proto.TXStatusError, + DiagnosticEvents: "test-diagnostic-events", + }, + expectedResponse: TransactionSubmissionResponse{ + ErrorResultXDR: "test-error", + DiagnosticEventsXDR: "test-diagnostic-events", + TxStatus: proto.TXStatusError, + HttpStatus: HttpStatusCodeForError, + Hash: TxHash, + }, + }, + { + mockCoreResponse: &proto.TXResponse{ + Status: proto.TXStatusPending, + }, + expectedResponse: TransactionSubmissionResponse{ + TxStatus: proto.TXStatusPending, + HttpStatus: HttpStatusCodeForPending, + Hash: TxHash, + }, + }, + { + mockCoreResponse: &proto.TXResponse{ + Status: proto.TXStatusDuplicate, + }, + expectedResponse: TransactionSubmissionResponse{ + TxStatus: proto.TXStatusDuplicate, + HttpStatus: HttpStatusCodeForDuplicate, + Hash: TxHash, + }, + }, + { + mockCoreResponse: &proto.TXResponse{ + Status: proto.TXStatusTryAgainLater, + }, + expectedResponse: TransactionSubmissionResponse{ + TxStatus: proto.TXStatusTryAgainLater, + HttpStatus: HttpStatusCodeForTryAgainLater, + Hash: TxHash, + }, + }, + } + + for _, testCase := range successCases { + mockCoreClient := &stellarcore.MockClient{} + mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) + + handler := AsyncSubmitTransactionHandler{ + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClient: mockCoreClient, + CoreStateGetter: coreStateGetter, + } + + request := createRequest() + w := httptest.NewRecorder() + + resp, err := handler.GetResource(w, request) + assert.NoError(t, err) + assert.Equal(t, resp, testCase.expectedResponse) + } +} From 6ff4fa26895639ac2227b6e45bf2cf6eb23fa677 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 31 Jan 2024 15:13:30 -0500 Subject: [PATCH 07/68] Fix goimports --- clients/stellarcore/mocks.go | 3 ++- .../internal/actions/submit_transaction_async.go | 5 +++-- .../internal/actions/submit_transaction_async_test.go | 11 ++++++----- services/horizon/internal/httpx/router.go | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go index f103fda7dc..372846897d 100644 --- a/clients/stellarcore/mocks.go +++ b/clients/stellarcore/mocks.go @@ -2,10 +2,11 @@ package stellarcore import ( "context" - "github.com/stellar/go/support/http/httptest" "net/http" "net/url" + "github.com/stellar/go/support/http/httptest" + proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/xdr" "github.com/stretchr/testify/mock" diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index d6f63638df..71c64b14c6 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -2,6 +2,9 @@ package actions import ( "encoding/hex" + "mime" + "net/http" + "github.com/stellar/go/clients/stellarcore" "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" @@ -9,8 +12,6 @@ import ( "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/problem" "github.com/stellar/go/xdr" - "mime" - "net/http" ) const ( diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 1c5de7dbab..348ac5d1c8 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -2,6 +2,12 @@ package actions import ( "context" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "github.com/stellar/go/clients/stellarcore" "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" @@ -9,11 +15,6 @@ import ( "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/problem" "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" ) const ( diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 58754217d3..0cedfa5cab 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -3,12 +3,13 @@ package httpx import ( "compress/flate" "fmt" - "github.com/stellar/go/clients/stellarcore" "net/http" "net/http/pprof" "net/url" "time" + "github.com/stellar/go/clients/stellarcore" + "github.com/go-chi/chi" chimiddleware "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus" From 525fb0edbda1d4f73d21c5f86c563d0ddcd41bfc Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 31 Jan 2024 15:40:07 -0500 Subject: [PATCH 08/68] Rearrange code and remove duplicate code --- .../internal/actions/submit_transaction.go | 55 +--------------- .../actions/submit_transaction_async.go | 65 +++---------------- .../actions/submit_transaction_async_test.go | 8 +-- services/horizon/internal/actions/utils.go | 58 +++++++++++++++++ 4 files changed, 74 insertions(+), 112 deletions(-) create mode 100644 services/horizon/internal/actions/utils.go diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index b877f75a7b..3b5ba77dca 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -2,17 +2,13 @@ package actions import ( "context" - "encoding/hex" - "mime" "net/http" - "github.com/stellar/go/network" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/services/horizon/internal/resourceadapter" "github.com/stellar/go/services/horizon/internal/txsub" - "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/hal" "github.com/stellar/go/support/render/problem" "github.com/stellar/go/xdr" @@ -29,53 +25,6 @@ type SubmitTransactionHandler struct { CoreStateGetter } -type envelopeInfo struct { - hash string - innerHash string - raw string - parsed xdr.TransactionEnvelope -} - -func (handler SubmitTransactionHandler) extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { - result := envelopeInfo{raw: raw} - err := xdr.SafeUnmarshalBase64(raw, &result.parsed) - if err != nil { - return result, err - } - - var hash [32]byte - hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) - if err != nil { - return result, err - } - result.hash = hex.EncodeToString(hash[:]) - if result.parsed.IsFeeBump() { - hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) - if err != nil { - return result, err - } - result.innerHash = hex.EncodeToString(hash[:]) - } - return result, nil -} - -func (handler SubmitTransactionHandler) validateBodyType(r *http.Request) error { - c := r.Header.Get("Content-Type") - if c == "" { - return nil - } - - mt, _, err := mime.ParseMediaType(c) - if err != nil { - return errors.Wrap(err, "Could not determine mime type") - } - - if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { - return &hProblem.UnsupportedMediaType - } - return nil -} - func (handler SubmitTransactionHandler) response(r *http.Request, info envelopeInfo, result txsub.Result) (hal.Pageable, error) { if result.Err == nil { var resource horizon.Transaction @@ -137,7 +86,7 @@ func (handler SubmitTransactionHandler) response(r *http.Request, info envelopeI } func (handler SubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { - if err := handler.validateBodyType(r); err != nil { + if err := validateBodyType(r); err != nil { return nil, err } @@ -157,7 +106,7 @@ func (handler SubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Requ return nil, err } - info, err := handler.extractEnvelopeInfo(raw, handler.NetworkPassphrase) + info, err := extractEnvelopeInfo(raw, handler.NetworkPassphrase) if err != nil { return nil, &problem.P{ Type: "transaction_malformed", diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 71c64b14c6..96a0372498 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -1,24 +1,19 @@ package actions import ( - "encoding/hex" - "mime" "net/http" "github.com/stellar/go/clients/stellarcore" - "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" - "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/problem" - "github.com/stellar/go/xdr" ) const ( - HttpStatusCodeForPending = http.StatusCreated - HttpStatusCodeForDuplicate = http.StatusConflict - HttpStatusCodeForTryAgainLater = http.StatusServiceUnavailable - HttpStatusCodeForError = http.StatusBadRequest + HTTPStatusCodeForPending = http.StatusCreated + HTTPStatusCodeForDuplicate = http.StatusConflict + HTTPStatusCodeForTryAgainLater = http.StatusServiceUnavailable + HTTPStatusCodeForError = http.StatusBadRequest ) type AsyncSubmitTransactionHandler struct { @@ -49,48 +44,8 @@ type TransactionSubmissionResponse struct { Hash string `json:"hash"` } -func (handler AsyncSubmitTransactionHandler) extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { - result := envelopeInfo{raw: raw} - err := xdr.SafeUnmarshalBase64(raw, &result.parsed) - if err != nil { - return result, err - } - - var hash [32]byte - hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) - if err != nil { - return result, err - } - result.hash = hex.EncodeToString(hash[:]) - if result.parsed.IsFeeBump() { - hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) - if err != nil { - return result, err - } - result.innerHash = hex.EncodeToString(hash[:]) - } - return result, nil -} - -func (handler AsyncSubmitTransactionHandler) validateBodyType(r *http.Request) error { - c := r.Header.Get("Content-Type") - if c == "" { - return nil - } - - mt, _, err := mime.ParseMediaType(c) - if err != nil { - return errors.Wrap(err, "Could not determine mime type") - } - - if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { - return &hProblem.UnsupportedMediaType - } - return nil -} - func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { - if err := handler.validateBodyType(r); err != nil { + if err := validateBodyType(r); err != nil { return nil, err } @@ -112,7 +67,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } } - info, err := handler.extractEnvelopeInfo(raw, handler.NetworkPassphrase) + info, err := extractEnvelopeInfo(raw, handler.NetworkPassphrase) if err != nil { return nil, &problem.P{ Type: "transaction_malformed", @@ -173,17 +128,17 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, TxStatus: resp.Status, - HttpStatus: HttpStatusCodeForError, + HttpStatus: HTTPStatusCodeForError, Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: var httpStatus int if resp.Status == proto.TXStatusPending { - httpStatus = HttpStatusCodeForPending + httpStatus = HTTPStatusCodeForPending } else if resp.Status == proto.TXStatusDuplicate { - httpStatus = HttpStatusCodeForDuplicate + httpStatus = HTTPStatusCodeForDuplicate } else { - httpStatus = HttpStatusCodeForTryAgainLater + httpStatus = HTTPStatusCodeForTryAgainLater } return TransactionSubmissionResponse{ diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 348ac5d1c8..b2bd1b59b5 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -153,7 +153,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { ErrorResultXDR: "test-error", DiagnosticEventsXDR: "test-diagnostic-events", TxStatus: proto.TXStatusError, - HttpStatus: HttpStatusCodeForError, + HttpStatus: HTTPStatusCodeForError, Hash: TxHash, }, }, @@ -163,7 +163,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { }, expectedResponse: TransactionSubmissionResponse{ TxStatus: proto.TXStatusPending, - HttpStatus: HttpStatusCodeForPending, + HttpStatus: HTTPStatusCodeForPending, Hash: TxHash, }, }, @@ -173,7 +173,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { }, expectedResponse: TransactionSubmissionResponse{ TxStatus: proto.TXStatusDuplicate, - HttpStatus: HttpStatusCodeForDuplicate, + HttpStatus: HTTPStatusCodeForDuplicate, Hash: TxHash, }, }, @@ -183,7 +183,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { }, expectedResponse: TransactionSubmissionResponse{ TxStatus: proto.TXStatusTryAgainLater, - HttpStatus: HttpStatusCodeForTryAgainLater, + HttpStatus: HTTPStatusCodeForTryAgainLater, Hash: TxHash, }, }, diff --git a/services/horizon/internal/actions/utils.go b/services/horizon/internal/actions/utils.go new file mode 100644 index 0000000000..1ad32b5d4a --- /dev/null +++ b/services/horizon/internal/actions/utils.go @@ -0,0 +1,58 @@ +package actions + +import ( + "encoding/hex" + "github.com/stellar/go/network" + hProblem "github.com/stellar/go/services/horizon/internal/render/problem" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/xdr" + "mime" + "net/http" +) + +type envelopeInfo struct { + hash string + innerHash string + raw string + parsed xdr.TransactionEnvelope +} + +func extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { + result := envelopeInfo{raw: raw} + err := xdr.SafeUnmarshalBase64(raw, &result.parsed) + if err != nil { + return result, err + } + + var hash [32]byte + hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) + if err != nil { + return result, err + } + result.hash = hex.EncodeToString(hash[:]) + if result.parsed.IsFeeBump() { + hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) + if err != nil { + return result, err + } + result.innerHash = hex.EncodeToString(hash[:]) + } + return result, nil +} + +func validateBodyType(r *http.Request) error { + c := r.Header.Get("Content-Type") + if c == "" { + return nil + } + + mt, _, err := mime.ParseMediaType(c) + if err != nil { + return errors.Wrap(err, "Could not determine mime type") + } + + if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { + return &hProblem.UnsupportedMediaType + } + return nil +} From d425558ece8060147f0d794d7a370016d2592711 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 12 Feb 2024 12:02:21 -0500 Subject: [PATCH 09/68] Add metrics - 1 --- services/horizon/internal/app.go | 36 +++++++++--------- .../horizon/internal/async-txsub/metrics.go | 38 +++++++++++++++++++ services/horizon/internal/httpx/router.go | 2 +- 3 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 services/horizon/internal/async-txsub/metrics.go diff --git a/services/horizon/internal/app.go b/services/horizon/internal/app.go index 98c1ed408e..c2c55bc194 100644 --- a/services/horizon/internal/app.go +++ b/services/horizon/internal/app.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + async_txsub "github.com/stellar/go/services/horizon/internal/async-txsub" "net/http" "os" "os/signal" @@ -33,23 +34,24 @@ import ( // App represents the root of the state of a horizon instance. type App struct { - done chan struct{} - doneOnce sync.Once - config Config - webServer *httpx.Server - historyQ *history.Q - primaryHistoryQ *history.Q - ctx context.Context - cancel func() - horizonVersion string - coreState corestate.Store - orderBookStream *ingest.OrderBookStream - submitter *txsub.System - paths paths.Finder - ingester ingest.System - reaper *reap.System - ticks *time.Ticker - ledgerState *ledger.State + done chan struct{} + doneOnce sync.Once + config Config + webServer *httpx.Server + historyQ *history.Q + primaryHistoryQ *history.Q + ctx context.Context + cancel func() + horizonVersion string + coreState corestate.Store + orderBookStream *ingest.OrderBookStream + submitter *txsub.System + paths paths.Finder + ingester ingest.System + reaper *reap.System + ticks *time.Ticker + ledgerState *ledger.State + asyncTxSubMetrics async_txsub.AsyncTxSubMetrics // metrics prometheusRegistry *prometheus.Registry diff --git a/services/horizon/internal/async-txsub/metrics.go b/services/horizon/internal/async-txsub/metrics.go new file mode 100644 index 0000000000..29bc4dddfb --- /dev/null +++ b/services/horizon/internal/async-txsub/metrics.go @@ -0,0 +1,38 @@ +package async_txsub + +import "github.com/prometheus/client_golang/prometheus" + +type AsyncTxSubMetrics struct { + // SubmissionDuration exposes timing metrics about the rate and latency of + // submissions to stellar-core + SubmissionDuration prometheus.Summary + + // OpenSubmissionsGauge tracks the count of "open" submissions (i.e. + // submissions whose transactions haven't been confirmed successful or failed + OpenSubmissionsGauge prometheus.Gauge + + // FailedSubmissionsCounter tracks the rate of failed transactions that have + // been submitted to this process + FailedSubmissionsCounter prometheus.Counter + + // SuccessfulSubmissionsCounter tracks the rate of successful transactions that + // have been submitted to this process + SuccessfulSubmissionsCounter prometheus.Counter + + // V0TransactionsCounter tracks the rate of v0 transaction envelopes that + // have been submitted to this process + V0TransactionsCounter prometheus.Counter + + // V1TransactionsCounter tracks the rate of v1 transaction envelopes that + // have been submitted to this process + V1TransactionsCounter prometheus.Counter + + // FeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that + // have been submitted to this process + FeeBumpTransactionsCounter prometheus.Counter +} + +func (metrics *AsyncTxSubMetrics) MustRegister(registry *prometheus.Registry) { + registry.MustRegister(metrics.FailedSubmissionsCounter) + registry.MustRegister(metrics.OpenSubmissionsGauge) +} diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 0cedfa5cab..6bd751cc3c 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -335,7 +335,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate }}) // Async Transaction submission API - r.Method(http.MethodPost, "/v2/transactions", ObjectActionHandler{actions.AsyncSubmitTransactionHandler{ + r.Method(http.MethodPost, "/transactions-async", ObjectActionHandler{actions.AsyncSubmitTransactionHandler{ NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, From 326e21ea575e75a5008cb2eecf3881bdc5ec3d9e Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 20 Feb 2024 17:20:35 -0500 Subject: [PATCH 10/68] Add metrics - 2 --- clients/stellarcore/core_client.go | 115 ++++++++++++++++++ clients/stellarcore/mocks.go | 58 +-------- .../actions/submit_transaction_async.go | 13 +- .../actions/submit_transaction_async_test.go | 32 ++--- services/horizon/internal/app.go | 36 +++--- .../horizon/internal/async-txsub/metrics.go | 38 ------ services/horizon/internal/httpx/router.go | 7 +- 7 files changed, 159 insertions(+), 140 deletions(-) create mode 100644 clients/stellarcore/core_client.go delete mode 100644 services/horizon/internal/async-txsub/metrics.go diff --git a/clients/stellarcore/core_client.go b/clients/stellarcore/core_client.go new file mode 100644 index 0000000000..640880bfc4 --- /dev/null +++ b/clients/stellarcore/core_client.go @@ -0,0 +1,115 @@ +package stellarcore + +import ( + "context" + "github.com/prometheus/client_golang/prometheus" + proto "github.com/stellar/go/protocols/stellarcore" + "time" +) + +type CoreClientWithMetricsInterface interface { + SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) +} + +type CoreClientWithMetrics struct { + CoreClient ClientInterface + + AsyncTxSubMetrics struct { + // AsyncSubmissionDuration exposes timing metrics about the rate and latency of + // submissions to stellar-core + AsyncSubmissionDuration *prometheus.SummaryVec + + // AsyncSubmissionsCounter tracks the rate of transactions that have + // been submitted to this process + AsyncSubmissionsCounter *prometheus.CounterVec + + // AsyncV0TransactionsCounter tracks the rate of v0 transaction envelopes that + // have been submitted to this process + AsyncV0TransactionsCounter *prometheus.CounterVec + + // AsyncV1TransactionsCounter tracks the rate of v1 transaction envelopes that + // have been submitted to this process + AsyncV1TransactionsCounter *prometheus.CounterVec + + // AsyncFeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that + // have been submitted to this process + AsyncFeeBumpTransactionsCounter *prometheus.CounterVec + } +} + +func (c *CoreClientWithMetrics) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { + startTime := time.Now() + response, err := c.CoreClient.SubmitTransaction(ctx, envelope) + duration := time.Since(startTime).Seconds() + + var label prometheus.Labels + if err != nil { + label = prometheus.Labels{"status": "request_error"} + } else if response.IsException() { + label = prometheus.Labels{"status": "exception"} + } else { + label = prometheus.Labels{"status": response.Status} + } + + c.AsyncTxSubMetrics.AsyncSubmissionDuration.With(label).Observe(duration) + return response, err +} + +func NewCoreClientWithMetrics(client Client, registry *prometheus.Registry) *CoreClientWithMetrics { + asyncSubmissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Namespace: "horizon", + Subsystem: "async_txsub", + Name: "submission_duration_seconds", + Help: "submission durations to Stellar-Core, sliding window = 10m", + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, + }, []string{"status"}) + asyncSubmissionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "horizon", + Subsystem: "async_txsub", + Name: "submissions_count", + Help: "number of submissions using the async txsub endpoint, sliding window = 10m", + }, []string{"status"}) + asyncV0TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "horizon", + Subsystem: "async_txsub", + Name: "v0_count", + Help: "number of v0 transaction envelopes submitted, sliding window = 10m", + }, []string{"status"}) + asyncV1TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "horizon", + Subsystem: "async_txsub", + Name: "v1_count", + Help: "number of v1 transaction envelopes submitted, sliding window = 10m", + }, []string{"status"}) + asyncFeeBumpTransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + Namespace: "horizon", + Subsystem: "async_txsub", + Name: "feebump_count", + Help: "number of fee bump transaction envelopes submitted, sliding window = 10m", + }, []string{"status"}) + + registry.MustRegister( + asyncSubmissionDuration, + asyncSubmissionsCounter, + asyncV0TransactionsCounter, + asyncV1TransactionsCounter, + asyncFeeBumpTransactionsCounter, + ) + + return &CoreClientWithMetrics{ + CoreClient: &client, + AsyncTxSubMetrics: struct { + AsyncSubmissionDuration *prometheus.SummaryVec + AsyncSubmissionsCounter *prometheus.CounterVec + AsyncV0TransactionsCounter *prometheus.CounterVec + AsyncV1TransactionsCounter *prometheus.CounterVec + AsyncFeeBumpTransactionsCounter *prometheus.CounterVec + }{ + AsyncSubmissionDuration: asyncSubmissionDuration, + AsyncSubmissionsCounter: asyncSubmissionsCounter, + AsyncV0TransactionsCounter: asyncV0TransactionsCounter, + AsyncV1TransactionsCounter: asyncV1TransactionsCounter, + AsyncFeeBumpTransactionsCounter: asyncFeeBumpTransactionsCounter, + }, + } +} diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go index 372846897d..5e2a437038 100644 --- a/clients/stellarcore/mocks.go +++ b/clients/stellarcore/mocks.go @@ -2,70 +2,16 @@ package stellarcore import ( "context" - "net/http" - "net/url" - - "github.com/stellar/go/support/http/httptest" - proto "github.com/stellar/go/protocols/stellarcore" - "github.com/stellar/go/xdr" "github.com/stretchr/testify/mock" ) -// MockClient is a mockable stellar-core client. -type MockClient struct { +type MockCoreClientWithMetrics struct { mock.Mock } -// Upgrade mocks the Upgrade method -func (m *MockClient) Upgrade(ctx context.Context, version int) error { - args := m.Called(ctx, version) - return args.Error(0) -} - -// GetLedgerEntry mocks the GetLedgerEntry method -func (m *MockClient) GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) { - args := m.Called(ctx, ledgerKey) - return args.Get(0).(proto.GetLedgerEntryResponse), args.Error(1) -} - -// Info mocks the Info method -func (m *MockClient) Info(ctx context.Context) (*proto.InfoResponse, error) { - args := m.Called(ctx) - return args.Get(0).(*proto.InfoResponse), args.Error(1) -} - -// SetCursor mocks the SetCursor method -func (m *MockClient) SetCursor(ctx context.Context, id string, cursor int32) error { - args := m.Called(ctx, id, cursor) - return args.Error(0) -} - // SubmitTransaction mocks the SubmitTransaction method -func (m *MockClient) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { +func (m *MockCoreClientWithMetrics) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { args := m.Called(ctx, envelope) return args.Get(0).(*proto.TXResponse), args.Error(1) } - -// WaitForNetworkSync mocks the WaitForNetworkSync method -func (m *MockClient) WaitForNetworkSync(ctx context.Context) error { - args := m.Called(ctx) - return args.Error(0) -} - -// ManualClose mocks the ManualClose method -func (m *MockClient) ManualClose(ctx context.Context) error { - args := m.Called(ctx) - return args.Error(0) -} - -// Mock for http() method -func (m *MockClient) http() HTTP { - return httptest.NewClient() -} - -// Mock for simpleGet() method -func (m *MockClient) simpleGet(ctx context.Context, newPath string, query url.Values) (*http.Request, error) { - args := m.Called(ctx, newPath, query) - return args.Get(0).(*http.Request), args.Error(1) -} diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 96a0372498..27e2318c58 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -1,12 +1,11 @@ package actions import ( - "net/http" - - "github.com/stellar/go/clients/stellarcore" + async_txsub "github.com/stellar/go/clients/stellarcore" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/support/render/problem" + "net/http" ) const ( @@ -17,9 +16,9 @@ const ( ) type AsyncSubmitTransactionHandler struct { - NetworkPassphrase string - DisableTxSub bool - CoreClient stellarcore.ClientInterface + NetworkPassphrase string + DisableTxSub bool + CoreClientWithMetrics async_txsub.CoreClientWithMetricsInterface CoreStateGetter } @@ -89,7 +88,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, hProblem.StaleHistory } - resp, err := handler.CoreClient.SubmitTransaction(r.Context(), info.raw) + resp, err := handler.CoreClientWithMetrics.SubmitTransaction(r.Context(), info.raw) if err != nil { return nil, &problem.P{ Type: "transaction_submission_failed", diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index b2bd1b59b5..b1f85ab380 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -2,13 +2,13 @@ package actions import ( "context" + stellarcore "github.com/stellar/go/clients/stellarcore" "net/http" "net/http/httptest" "net/url" "strings" "testing" - "github.com/stellar/go/clients/stellarcore" "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/services/horizon/internal/corestate" @@ -88,13 +88,13 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - mockCoreClient := &stellarcore.MockClient{} - mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) + mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} + mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) handler := AsyncSubmitTransactionHandler{ - CoreStateGetter: coreStateGetter, - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClient: mockCoreClient, + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClientWithMetrics: mockCoreClientWithMetrics, } request := createRequest() @@ -112,15 +112,15 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - mockCoreClient := &stellarcore.MockClient{} - mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ + mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} + mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ Exception: "some-exception", }, nil) handler := AsyncSubmitTransactionHandler{ - CoreStateGetter: coreStateGetter, - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClient: mockCoreClient, + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClientWithMetrics: mockCoreClientWithMetrics, } request := createRequest() @@ -190,13 +190,13 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { } for _, testCase := range successCases { - mockCoreClient := &stellarcore.MockClient{} - mockCoreClient.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) + mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} + mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) handler := AsyncSubmitTransactionHandler{ - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClient: mockCoreClient, - CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + CoreClientWithMetrics: mockCoreClientWithMetrics, + CoreStateGetter: coreStateGetter, } request := createRequest() diff --git a/services/horizon/internal/app.go b/services/horizon/internal/app.go index c2c55bc194..98c1ed408e 100644 --- a/services/horizon/internal/app.go +++ b/services/horizon/internal/app.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "fmt" - async_txsub "github.com/stellar/go/services/horizon/internal/async-txsub" "net/http" "os" "os/signal" @@ -34,24 +33,23 @@ import ( // App represents the root of the state of a horizon instance. type App struct { - done chan struct{} - doneOnce sync.Once - config Config - webServer *httpx.Server - historyQ *history.Q - primaryHistoryQ *history.Q - ctx context.Context - cancel func() - horizonVersion string - coreState corestate.Store - orderBookStream *ingest.OrderBookStream - submitter *txsub.System - paths paths.Finder - ingester ingest.System - reaper *reap.System - ticks *time.Ticker - ledgerState *ledger.State - asyncTxSubMetrics async_txsub.AsyncTxSubMetrics + done chan struct{} + doneOnce sync.Once + config Config + webServer *httpx.Server + historyQ *history.Q + primaryHistoryQ *history.Q + ctx context.Context + cancel func() + horizonVersion string + coreState corestate.Store + orderBookStream *ingest.OrderBookStream + submitter *txsub.System + paths paths.Finder + ingester ingest.System + reaper *reap.System + ticks *time.Ticker + ledgerState *ledger.State // metrics prometheusRegistry *prometheus.Registry diff --git a/services/horizon/internal/async-txsub/metrics.go b/services/horizon/internal/async-txsub/metrics.go deleted file mode 100644 index 29bc4dddfb..0000000000 --- a/services/horizon/internal/async-txsub/metrics.go +++ /dev/null @@ -1,38 +0,0 @@ -package async_txsub - -import "github.com/prometheus/client_golang/prometheus" - -type AsyncTxSubMetrics struct { - // SubmissionDuration exposes timing metrics about the rate and latency of - // submissions to stellar-core - SubmissionDuration prometheus.Summary - - // OpenSubmissionsGauge tracks the count of "open" submissions (i.e. - // submissions whose transactions haven't been confirmed successful or failed - OpenSubmissionsGauge prometheus.Gauge - - // FailedSubmissionsCounter tracks the rate of failed transactions that have - // been submitted to this process - FailedSubmissionsCounter prometheus.Counter - - // SuccessfulSubmissionsCounter tracks the rate of successful transactions that - // have been submitted to this process - SuccessfulSubmissionsCounter prometheus.Counter - - // V0TransactionsCounter tracks the rate of v0 transaction envelopes that - // have been submitted to this process - V0TransactionsCounter prometheus.Counter - - // V1TransactionsCounter tracks the rate of v1 transaction envelopes that - // have been submitted to this process - V1TransactionsCounter prometheus.Counter - - // FeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that - // have been submitted to this process - FeeBumpTransactionsCounter prometheus.Counter -} - -func (metrics *AsyncTxSubMetrics) MustRegister(registry *prometheus.Registry) { - registry.MustRegister(metrics.FailedSubmissionsCounter) - registry.MustRegister(metrics.OpenSubmissionsGauge) -} diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 6bd751cc3c..e323b5fd2e 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -3,13 +3,12 @@ package httpx import ( "compress/flate" "fmt" + "github.com/stellar/go/clients/stellarcore" "net/http" "net/http/pprof" "net/url" "time" - "github.com/stellar/go/clients/stellarcore" - "github.com/go-chi/chi" chimiddleware "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus" @@ -339,10 +338,10 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, - CoreClient: &stellarcore.Client{ + CoreClientWithMetrics: stellarcore.NewCoreClientWithMetrics(stellarcore.Client{ HTTP: http.DefaultClient, URL: config.StellarCoreURL, - }, + }, config.PrometheusRegistry), }}) // Network state related endpoints From 37d01ab47848dc77035180c1fc0a7449e6e3e037 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 22 Feb 2024 15:55:21 -0500 Subject: [PATCH 11/68] Add metrics - 3 --- .gitignore | 1 + .../{core_client.go => metrics_client.go} | 33 ++++++++++++++----- clients/stellarcore/mocks.go | 11 +++++-- .../actions/submit_transaction_async.go | 14 ++++---- .../actions/submit_transaction_async_test.go | 30 ++++++++--------- services/horizon/internal/httpx/router.go | 2 +- 6 files changed, 56 insertions(+), 35 deletions(-) rename clients/stellarcore/{core_client.go => metrics_client.go} (74%) diff --git a/.gitignore b/.gitignore index e4760a698c..c505323c66 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /services/horizon/captive-core /services/horizon/horizon /services/horizon/stellar-horizon +/bucket-cache .vscode .idea debug diff --git a/clients/stellarcore/core_client.go b/clients/stellarcore/metrics_client.go similarity index 74% rename from clients/stellarcore/core_client.go rename to clients/stellarcore/metrics_client.go index 640880bfc4..ea58d3134a 100644 --- a/clients/stellarcore/core_client.go +++ b/clients/stellarcore/metrics_client.go @@ -4,14 +4,16 @@ import ( "context" "github.com/prometheus/client_golang/prometheus" proto "github.com/stellar/go/protocols/stellarcore" + "github.com/stellar/go/xdr" "time" ) -type CoreClientWithMetricsInterface interface { - SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) +type ClientWithMetricsInterface interface { + SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) + UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) } -type CoreClientWithMetrics struct { +type ClientWithMetrics struct { CoreClient ClientInterface AsyncTxSubMetrics struct { @@ -37,11 +39,15 @@ type CoreClientWithMetrics struct { } } -func (c *CoreClientWithMetrics) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { +func (c *ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { startTime := time.Now() - response, err := c.CoreClient.SubmitTransaction(ctx, envelope) - duration := time.Since(startTime).Seconds() + response, err := c.CoreClient.SubmitTransaction(ctx, rawTx) + c.UpdateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) + return response, err +} + +func (c *ClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { var label prometheus.Labels if err != nil { label = prometheus.Labels{"status": "request_error"} @@ -52,10 +58,19 @@ func (c *CoreClientWithMetrics) SubmitTransaction(ctx context.Context, envelope } c.AsyncTxSubMetrics.AsyncSubmissionDuration.With(label).Observe(duration) - return response, err + c.AsyncTxSubMetrics.AsyncSubmissionsCounter.With(label).Inc() + + switch envelope.Type { + case xdr.EnvelopeTypeEnvelopeTypeTxV0: + c.AsyncTxSubMetrics.AsyncV0TransactionsCounter.With(label).Inc() + case xdr.EnvelopeTypeEnvelopeTypeTx: + c.AsyncTxSubMetrics.AsyncV1TransactionsCounter.With(label).Inc() + case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: + c.AsyncTxSubMetrics.AsyncFeeBumpTransactionsCounter.With(label).Inc() + } } -func NewCoreClientWithMetrics(client Client, registry *prometheus.Registry) *CoreClientWithMetrics { +func NewClientWithMetrics(client Client, registry *prometheus.Registry) *ClientWithMetrics { asyncSubmissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: "horizon", Subsystem: "async_txsub", @@ -96,7 +111,7 @@ func NewCoreClientWithMetrics(client Client, registry *prometheus.Registry) *Cor asyncFeeBumpTransactionsCounter, ) - return &CoreClientWithMetrics{ + return &ClientWithMetrics{ CoreClient: &client, AsyncTxSubMetrics: struct { AsyncSubmissionDuration *prometheus.SummaryVec diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go index 5e2a437038..0f2392aa30 100644 --- a/clients/stellarcore/mocks.go +++ b/clients/stellarcore/mocks.go @@ -3,15 +3,20 @@ package stellarcore import ( "context" proto "github.com/stellar/go/protocols/stellarcore" + "github.com/stellar/go/xdr" "github.com/stretchr/testify/mock" ) -type MockCoreClientWithMetrics struct { +type MockClientWithMetrics struct { mock.Mock } // SubmitTransaction mocks the SubmitTransaction method -func (m *MockCoreClientWithMetrics) SubmitTransaction(ctx context.Context, envelope string) (*proto.TXResponse, error) { - args := m.Called(ctx, envelope) +func (m *MockClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { + args := m.Called(ctx, rawTx, envelope) return args.Get(0).(*proto.TXResponse), args.Error(1) } + +func (m *MockClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { + m.Called(duration, envelope, response, err) +} diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 27e2318c58..136a035d5b 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -16,9 +16,9 @@ const ( ) type AsyncSubmitTransactionHandler struct { - NetworkPassphrase string - DisableTxSub bool - CoreClientWithMetrics async_txsub.CoreClientWithMetricsInterface + NetworkPassphrase string + DisableTxSub bool + ClientWithMetrics async_txsub.ClientWithMetricsInterface CoreStateGetter } @@ -88,7 +88,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, hProblem.StaleHistory } - resp, err := handler.CoreClientWithMetrics.SubmitTransaction(r.Context(), info.raw) + resp, err := handler.ClientWithMetrics.SubmitTransaction(r.Context(), info.raw, info.parsed) if err != nil { return nil, &problem.P{ Type: "transaction_submission_failed", @@ -97,7 +97,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http Detail: "Could not submit transaction to stellar-core. " + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + - "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_failed", + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_failed", Extras: map[string]interface{}{ "envelope_xdr": raw, "error": err, @@ -113,7 +113,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http Detail: "Received exception from stellar-core." + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + - "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_exception", + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_exception", Extras: map[string]interface{}{ "envelope_xdr": raw, "error": resp.Exception, @@ -153,7 +153,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http Detail: "Received invalid status from stellar-core." + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + - "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-v2/transaction_submission_invalid_status", + "https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_invalid_status", Extras: map[string]interface{}{ "envelope_xdr": raw, "error": resp.Error, diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index b1f85ab380..b74522ff48 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -88,13 +88,13 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} - mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) + MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) handler := AsyncSubmitTransactionHandler{ - CoreStateGetter: coreStateGetter, - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClientWithMetrics: mockCoreClientWithMetrics, + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + ClientWithMetrics: MockClientWithMetrics, } request := createRequest() @@ -112,15 +112,15 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} - mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ + MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ Exception: "some-exception", }, nil) handler := AsyncSubmitTransactionHandler{ - CoreStateGetter: coreStateGetter, - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClientWithMetrics: mockCoreClientWithMetrics, + CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + ClientWithMetrics: MockClientWithMetrics, } request := createRequest() @@ -190,13 +190,13 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { } for _, testCase := range successCases { - mockCoreClientWithMetrics := &stellarcore.MockCoreClientWithMetrics{} - mockCoreClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) + MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) handler := AsyncSubmitTransactionHandler{ - NetworkPassphrase: network.PublicNetworkPassphrase, - CoreClientWithMetrics: mockCoreClientWithMetrics, - CoreStateGetter: coreStateGetter, + NetworkPassphrase: network.PublicNetworkPassphrase, + ClientWithMetrics: MockClientWithMetrics, + CoreStateGetter: coreStateGetter, } request := createRequest() diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index e323b5fd2e..387c1f9195 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -338,7 +338,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, - CoreClientWithMetrics: stellarcore.NewCoreClientWithMetrics(stellarcore.Client{ + ClientWithMetrics: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: http.DefaultClient, URL: config.StellarCoreURL, }, config.PrometheusRegistry), From c7e46aff8070e73b8f53a8962b2ea80c17183fe7 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 23 Feb 2024 11:19:48 -0500 Subject: [PATCH 12/68] Fix failing unittest --- .../actions/submit_transaction_async.go | 10 +++---- .../actions/submit_transaction_async_test.go | 29 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 136a035d5b..66dab4b383 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -22,9 +22,9 @@ type AsyncSubmitTransactionHandler struct { CoreStateGetter } -// TransactionSubmissionResponse represents the response returned by Horizon -// when using the transaction-submission-v2 endpoint. -type TransactionSubmissionResponse struct { +// AsyncTransactionSubmissionResponse represents the response returned by Horizon +// when using the transaction-submission-async endpoint. +type AsyncTransactionSubmissionResponse struct { // ErrorResultXDR is present only if Status is equal to proto.TXStatusError. // ErrorResultXDR is a TransactionResult xdr string which contains details on why // the transaction could not be accepted by stellar-core. @@ -123,7 +123,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http switch resp.Status { case proto.TXStatusError: - return TransactionSubmissionResponse{ + return AsyncTransactionSubmissionResponse{ ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, TxStatus: resp.Status, @@ -140,7 +140,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http httpStatus = HTTPStatusCodeForTryAgainLater } - return TransactionSubmissionResponse{ + return AsyncTransactionSubmissionResponse{ TxStatus: resp.Status, HttpStatus: httpStatus, Hash: info.hash, diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index b74522ff48..5d8994a448 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -88,8 +88,11 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) + assert.NoError(t, err) + MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(&proto.TXResponse{}, errors.Errorf("submission error")) handler := AsyncSubmitTransactionHandler{ CoreStateGetter: coreStateGetter, @@ -100,7 +103,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) request := createRequest() w := httptest.NewRecorder() - _, err := handler.GetResource(w, request) + _, err = handler.GetResource(w, request) assert.NotNil(t, err) assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) @@ -112,8 +115,11 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) + assert.NoError(t, err) + MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(&proto.TXResponse{ + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(&proto.TXResponse{ Exception: "some-exception", }, nil) @@ -126,7 +132,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing request := createRequest() w := httptest.NewRecorder() - _, err := handler.GetResource(w, request) + _, err = handler.GetResource(w, request) assert.NotNil(t, err) assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) @@ -138,9 +144,12 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) + info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) + assert.NoError(t, err) + successCases := []struct { mockCoreResponse *proto.TXResponse - expectedResponse TransactionSubmissionResponse + expectedResponse AsyncTransactionSubmissionResponse }{ { mockCoreResponse: &proto.TXResponse{ @@ -149,7 +158,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { Status: proto.TXStatusError, DiagnosticEvents: "test-diagnostic-events", }, - expectedResponse: TransactionSubmissionResponse{ + expectedResponse: AsyncTransactionSubmissionResponse{ ErrorResultXDR: "test-error", DiagnosticEventsXDR: "test-diagnostic-events", TxStatus: proto.TXStatusError, @@ -161,7 +170,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusPending, }, - expectedResponse: TransactionSubmissionResponse{ + expectedResponse: AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusPending, HttpStatus: HTTPStatusCodeForPending, Hash: TxHash, @@ -171,7 +180,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusDuplicate, }, - expectedResponse: TransactionSubmissionResponse{ + expectedResponse: AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusDuplicate, HttpStatus: HTTPStatusCodeForDuplicate, Hash: TxHash, @@ -181,7 +190,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusTryAgainLater, }, - expectedResponse: TransactionSubmissionResponse{ + expectedResponse: AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusTryAgainLater, HttpStatus: HTTPStatusCodeForTryAgainLater, Hash: TxHash, @@ -191,7 +200,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { for _, testCase := range successCases { MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) + MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(testCase.mockCoreResponse, nil) handler := AsyncSubmitTransactionHandler{ NetworkPassphrase: network.PublicNetworkPassphrase, From ed812eacbdabe5ca1ac6d1a954e7a6c3a797a660 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 23 Feb 2024 16:37:58 -0500 Subject: [PATCH 13/68] Add new endpoint to go sdk + integration test --- clients/horizonclient/client.go | 81 ++++++++++++ clients/horizonclient/main.go | 5 + clients/horizonclient/mocks.go | 30 +++++ protocols/horizon/main.go | 21 ++++ .../internal/integration/async_txsub_test.go | 118 ++++++++++++++++++ .../internal/test/integration/integration.go | 16 +++ 6 files changed, 271 insertions(+) create mode 100644 services/horizon/internal/integration/async_txsub_test.go diff --git a/clients/horizonclient/client.go b/clients/horizonclient/client.go index b27b400364..c35030a8f2 100644 --- a/clients/horizonclient/client.go +++ b/clients/horizonclient/client.go @@ -517,6 +517,87 @@ func (c *Client) SubmitTransactionWithOptions(transaction *txnbuild.Transaction, return c.SubmitTransactionXDR(txeBase64) } +// AsyncSubmitTransactionXDR submits a transaction represented as a base64 XDR string to the network. err can be either error object or horizon.Error object. +// See https://developers.stellar.org/api/resources/transactions/post/ +func (c *Client) AsyncSubmitTransactionXDR(transactionXdr string) (txResp hProtocol.AsyncTransactionSubmissionResponse, + err error) { + request := submitRequest{endpoint: "transactions-async", transactionXdr: transactionXdr} + err = c.sendRequest(request, &txResp) + return +} + +// AsyncSubmitFeeBumpTransaction submits a fee bump transaction to the network. err can be either an +// error object or a horizon.Error object. +// +// This function will always check if the destination account requires a memo in the transaction as +// defined in SEP0029: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md +// +// If you want to skip this check, use SubmitTransactionWithOptions. +// +// See https://developers.stellar.org/api/resources/transactions/post/ +func (c *Client) AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { + return c.AsyncSubmitFeeBumpTransactionWithOptions(transaction, SubmitTxOpts{}) +} + +// AsyncSubmitFeeBumpTransactionWithOptions submits a fee bump transaction to the network, allowing +// you to pass SubmitTxOpts. err can be either an error object or a horizon.Error object. +// +// See https://developers.stellar.org/api/resources/transactions/post/ +func (c *Client) AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { + // only check if memo is required if skip is false and the inner transaction + // doesn't have a memo. + if inner := transaction.InnerTransaction(); !opts.SkipMemoRequiredCheck && inner.Memo() == nil { + err = c.checkMemoRequired(inner) + if err != nil { + return + } + } + + txeBase64, err := transaction.Base64() + if err != nil { + err = errors.Wrap(err, "Unable to convert transaction object to base64 string") + return + } + + return c.AsyncSubmitTransactionXDR(txeBase64) +} + +// AsyncSubmitTransaction submits a transaction to the network. err can be either an +// error object or a horizon.Error object. +// +// This function will always check if the destination account requires a memo in the transaction as +// defined in SEP0029: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md +// +// If you want to skip this check, use SubmitTransactionWithOptions. +// +// See https://developers.stellar.org/api/resources/transactions/post/ +func (c *Client) AsyncSubmitTransaction(transaction *txnbuild.Transaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { + return c.AsyncSubmitTransactionWithOptions(transaction, SubmitTxOpts{}) +} + +// AsyncSubmitTransactionWithOptions submits a transaction to the network, allowing +// you to pass SubmitTxOpts. err can be either an error object or a horizon.Error object. +// +// See https://developers.stellar.org/api/resources/transactions/post/ +func (c *Client) AsyncSubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { + // only check if memo is required if skip is false and the transaction + // doesn't have a memo. + if !opts.SkipMemoRequiredCheck && transaction.Memo() == nil { + err = c.checkMemoRequired(transaction) + if err != nil { + return + } + } + + txeBase64, err := transaction.Base64() + if err != nil { + err = errors.Wrap(err, "Unable to convert transaction object to base64 string") + return + } + + return c.AsyncSubmitTransactionXDR(txeBase64) +} + // Transactions returns stellar transactions (https://developers.stellar.org/api/resources/transactions/list/) // It can be used to return transactions for an account, a ledger,and all transactions on the network. func (c *Client) Transactions(request TransactionRequest) (txs hProtocol.TransactionsPage, err error) { diff --git a/clients/horizonclient/main.go b/clients/horizonclient/main.go index 8eb7bdc606..3a1b9e4b71 100644 --- a/clients/horizonclient/main.go +++ b/clients/horizonclient/main.go @@ -191,6 +191,11 @@ type ClientInterface interface { SubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (hProtocol.Transaction, error) SubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (hProtocol.Transaction, error) SubmitTransaction(transaction *txnbuild.Transaction) (hProtocol.Transaction, error) + AsyncSubmitTransactionXDR(transactionXdr string) (hProtocol.AsyncTransactionSubmissionResponse, error) + AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (hProtocol.AsyncTransactionSubmissionResponse, error) + AsyncSubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (hProtocol.AsyncTransactionSubmissionResponse, error) + AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (hProtocol.AsyncTransactionSubmissionResponse, error) + AsyncSubmitTransaction(transaction *txnbuild.Transaction) (hProtocol.AsyncTransactionSubmissionResponse, error) Transactions(request TransactionRequest) (hProtocol.TransactionsPage, error) TransactionDetail(txHash string) (hProtocol.Transaction, error) OrderBook(request OrderBookRequest) (hProtocol.OrderBookSummary, error) diff --git a/clients/horizonclient/mocks.go b/clients/horizonclient/mocks.go index fbf6fe5b66..92c766dd54 100644 --- a/clients/horizonclient/mocks.go +++ b/clients/horizonclient/mocks.go @@ -121,6 +121,36 @@ func (m *MockClient) SubmitTransactionWithOptions(transaction *txnbuild.Transact return a.Get(0).(hProtocol.Transaction), a.Error(1) } +// AsyncSubmitTransactionXDR is a mocking method +func (m *MockClient) AsyncSubmitTransactionXDR(transactionXdr string) (hProtocol.AsyncTransactionSubmissionResponse, error) { + a := m.Called(transactionXdr) + return a.Get(0).(hProtocol.AsyncTransactionSubmissionResponse), a.Error(1) +} + +// AsyncSubmitFeeBumpTransaction is a mocking method +func (m *MockClient) AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (hProtocol.AsyncTransactionSubmissionResponse, error) { + a := m.Called(transaction) + return a.Get(0).(hProtocol.AsyncTransactionSubmissionResponse), a.Error(1) +} + +// AsyncSubmitTransaction is a mocking method +func (m *MockClient) AsyncSubmitTransaction(transaction *txnbuild.Transaction) (hProtocol.AsyncTransactionSubmissionResponse, error) { + a := m.Called(transaction) + return a.Get(0).(hProtocol.AsyncTransactionSubmissionResponse), a.Error(1) +} + +// AsyncSubmitFeeBumpTransactionWithOptions is a mocking method +func (m *MockClient) AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (hProtocol.AsyncTransactionSubmissionResponse, error) { + a := m.Called(transaction, opts) + return a.Get(0).(hProtocol.AsyncTransactionSubmissionResponse), a.Error(1) +} + +// AsyncSubmitTransactionWithOptions is a mocking method +func (m *MockClient) AsyncSubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (hProtocol.AsyncTransactionSubmissionResponse, error) { + a := m.Called(transaction, opts) + return a.Get(0).(hProtocol.AsyncTransactionSubmissionResponse), a.Error(1) +} + // Transactions is a mocking method func (m *MockClient) Transactions(request TransactionRequest) (hProtocol.TransactionsPage, error) { a := m.Called(request) diff --git a/protocols/horizon/main.go b/protocols/horizon/main.go index 08da47b7b4..1b61a7bf90 100644 --- a/protocols/horizon/main.go +++ b/protocols/horizon/main.go @@ -567,6 +567,27 @@ type InnerTransaction struct { MaxFee int64 `json:"max_fee,string"` } +// AsyncTransactionSubmissionResponse represents the response returned by Horizon +// when using the transaction-async endpoint. +type AsyncTransactionSubmissionResponse struct { + // ErrorResultXDR is present only if Status is equal to proto.TXStatusError. + // ErrorResultXDR is a TransactionResult xdr string which contains details on why + // the transaction could not be accepted by stellar-core. + ErrorResultXDR string `json:"errorResultXdr,omitempty"` + // DiagnosticEventsXDR is present only if Status is equal to proto.TXStatusError. + // DiagnosticEventsXDR is a base64-encoded slice of xdr.DiagnosticEvent + DiagnosticEventsXDR string `json:"diagnosticEventsXdr,omitempty"` + // TxStatus represents the status of the transaction submission returned by stellar-core. + // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, + // proto.TXStatusTryAgainLater, or proto.TXStatusError. + TxStatus string `json:"tx_status"` + // HttpStatus represents the corresponding http status code. + HttpStatus int `json:"status"` + // Hash is a hash of the transaction which can be used to look up whether + // the transaction was included in the ledger. + Hash string `json:"hash"` +} + // MarshalJSON implements a custom marshaler for Transaction. // The memo field should be omitted if and only if the // memo_type is "none". diff --git a/services/horizon/internal/integration/async_txsub_test.go b/services/horizon/internal/integration/async_txsub_test.go new file mode 100644 index 0000000000..d8ed79b69d --- /dev/null +++ b/services/horizon/internal/integration/async_txsub_test.go @@ -0,0 +1,118 @@ +package integration + +import ( + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/services/horizon/internal/test/integration" + "github.com/stellar/go/txnbuild" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { + itest := integration.NewTest(t, integration.Config{}) + master := itest.Master() + masterAccount := itest.MasterAccount() + + txParams := txnbuild.TransactionParams{ + BaseFee: txnbuild.MinBaseFee, + SourceAccount: masterAccount, + IncrementSequenceNum: true, + Operations: []txnbuild.Operation{ + &txnbuild.Payment{ + Destination: master.Address(), + Amount: "10", + Asset: txnbuild.NativeAsset{}, + }, + }, + Preconditions: txnbuild.Preconditions{ + TimeBounds: txnbuild.NewInfiniteTimeout(), + LedgerBounds: &txnbuild.LedgerBounds{MinLedger: 0, MaxLedger: 100}, + }, + } + + txResp, err := itest.AsyncSubmitTransaction(master, txParams) + assert.NoError(t, err) + assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ + ErrorResultXDR: "", + DiagnosticEventsXDR: "", + TxStatus: "PENDING", + HttpStatus: 201, + Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", + }) +} + +func TestAsyncTxSub_SubmissionError(t *testing.T) { + itest := integration.NewTest(t, integration.Config{}) + master := itest.Master() + masterAccount := itest.MasterAccount() + + txParams := txnbuild.TransactionParams{ + BaseFee: txnbuild.MinBaseFee, + SourceAccount: masterAccount, + IncrementSequenceNum: false, + Operations: []txnbuild.Operation{ + &txnbuild.Payment{ + Destination: master.Address(), + Amount: "10", + Asset: txnbuild.NativeAsset{}, + }, + }, + Preconditions: txnbuild.Preconditions{ + TimeBounds: txnbuild.NewInfiniteTimeout(), + LedgerBounds: &txnbuild.LedgerBounds{MinLedger: 0, MaxLedger: 100}, + }, + } + + txResp, err := itest.AsyncSubmitTransaction(master, txParams) + assert.NoError(t, err) + assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ + ErrorResultXDR: "AAAAAAAAAGT////7AAAAAA==", + DiagnosticEventsXDR: "", + TxStatus: "ERROR", + HttpStatus: 400, + Hash: "0684df00f20efd5876f1b8d17bc6d3a68d8b85c06bb41e448815ecaa6307a251", + }) +} + +func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) { + itest := integration.NewTest(t, integration.Config{}) + master := itest.Master() + masterAccount := itest.MasterAccount() + + txParams := txnbuild.TransactionParams{ + BaseFee: txnbuild.MinBaseFee, + SourceAccount: masterAccount, + IncrementSequenceNum: true, + Operations: []txnbuild.Operation{ + &txnbuild.Payment{ + Destination: master.Address(), + Amount: "10", + Asset: txnbuild.NativeAsset{}, + }, + }, + Preconditions: txnbuild.Preconditions{ + TimeBounds: txnbuild.NewInfiniteTimeout(), + LedgerBounds: &txnbuild.LedgerBounds{MinLedger: 0, MaxLedger: 100}, + }, + } + + txResp, err := itest.AsyncSubmitTransaction(master, txParams) + assert.NoError(t, err) + assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ + ErrorResultXDR: "", + DiagnosticEventsXDR: "", + TxStatus: "PENDING", + HttpStatus: 201, + Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", + }) + + txResp, err = itest.AsyncSubmitTransaction(master, txParams) + assert.NoError(t, err) + assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ + ErrorResultXDR: "", + DiagnosticEventsXDR: "", + TxStatus: "TRY_AGAIN_LATER", + HttpStatus: 503, + Hash: "d5eb72a4c1832b89965850fff0bd9bba4b6ca102e7c89099dcaba5e7d7d2e049", + }) +} diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 87718d5a67..6a6a27cc44 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -1151,6 +1151,22 @@ func (i *Test) SubmitMultiSigTransaction( return i.Client().SubmitTransaction(tx) } +func (i *Test) AsyncSubmitTransaction( + signer *keypair.Full, txParams txnbuild.TransactionParams, +) (proto.AsyncTransactionSubmissionResponse, error) { + return i.AsyncSubmitMultiSigTransaction([]*keypair.Full{signer}, txParams) +} + +func (i *Test) AsyncSubmitMultiSigTransaction( + signers []*keypair.Full, txParams txnbuild.TransactionParams, +) (proto.AsyncTransactionSubmissionResponse, error) { + tx, err := i.CreateSignedTransaction(signers, txParams) + if err != nil { + return proto.AsyncTransactionSubmissionResponse{}, err + } + return i.Client().AsyncSubmitTransaction(tx) +} + func (i *Test) MustSubmitMultiSigTransaction( signers []*keypair.Full, txParams txnbuild.TransactionParams, ) proto.Transaction { From 808a4d806b8342e48702133fcdad19643ee7417e Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 26 Feb 2024 12:34:41 -0500 Subject: [PATCH 14/68] Small changes - 1 --- clients/horizonclient/client.go | 19 +++++-------------- .../actions/submit_transaction_async.go | 4 ++-- ...sync_txsub_test.go => txsub_async_test.go} | 0 3 files changed, 7 insertions(+), 16 deletions(-) rename services/horizon/internal/integration/{async_txsub_test.go => txsub_async_test.go} (100%) diff --git a/clients/horizonclient/client.go b/clients/horizonclient/client.go index ff1b02b07c..40638f977f 100644 --- a/clients/horizonclient/client.go +++ b/clients/horizonclient/client.go @@ -521,8 +521,7 @@ func (c *Client) SubmitTransactionWithOptions(transaction *txnbuild.Transaction, return c.SubmitTransactionXDR(txeBase64) } -// AsyncSubmitTransactionXDR submits a transaction represented as a base64 XDR string to the network. err can be either error object or horizon.Error object. -// See https://developers.stellar.org/api/resources/transactions/post/ +// AsyncSubmitTransactionXDR submits a base64 XDR transaction using the transactions-async endpoint. err can be either error object or horizon.Error object. func (c *Client) AsyncSubmitTransactionXDR(transactionXdr string) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { request := submitRequest{endpoint: "transactions-async", transactionXdr: transactionXdr} @@ -530,23 +529,19 @@ func (c *Client) AsyncSubmitTransactionXDR(transactionXdr string) (txResp hProto return } -// AsyncSubmitFeeBumpTransaction submits a fee bump transaction to the network. err can be either an +// AsyncSubmitFeeBumpTransaction submits an async fee bump transaction to the network. err can be either an // error object or a horizon.Error object. // // This function will always check if the destination account requires a memo in the transaction as // defined in SEP0029: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md // // If you want to skip this check, use SubmitTransactionWithOptions. -// -// See https://developers.stellar.org/api/resources/transactions/post/ func (c *Client) AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { return c.AsyncSubmitFeeBumpTransactionWithOptions(transaction, SubmitTxOpts{}) } -// AsyncSubmitFeeBumpTransactionWithOptions submits a fee bump transaction to the network, allowing +// AsyncSubmitFeeBumpTransactionWithOptions submits an async fee bump transaction to the network, allowing // you to pass SubmitTxOpts. err can be either an error object or a horizon.Error object. -// -// See https://developers.stellar.org/api/resources/transactions/post/ func (c *Client) AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { // only check if memo is required if skip is false and the inner transaction // doesn't have a memo. @@ -566,23 +561,19 @@ func (c *Client) AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild. return c.AsyncSubmitTransactionXDR(txeBase64) } -// AsyncSubmitTransaction submits a transaction to the network. err can be either an +// AsyncSubmitTransaction submits an async transaction to the network. err can be either an // error object or a horizon.Error object. // // This function will always check if the destination account requires a memo in the transaction as // defined in SEP0029: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md // // If you want to skip this check, use SubmitTransactionWithOptions. -// -// See https://developers.stellar.org/api/resources/transactions/post/ func (c *Client) AsyncSubmitTransaction(transaction *txnbuild.Transaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { return c.AsyncSubmitTransactionWithOptions(transaction, SubmitTxOpts{}) } -// AsyncSubmitTransactionWithOptions submits a transaction to the network, allowing +// AsyncSubmitTransactionWithOptions submits an async transaction to the network, allowing // you to pass SubmitTxOpts. err can be either an error object or a horizon.Error object. -// -// See https://developers.stellar.org/api/resources/transactions/post/ func (c *Client) AsyncSubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { // only check if memo is required if skip is false and the transaction // doesn't have a memo. diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 66dab4b383..51f8fc04cb 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -1,7 +1,7 @@ package actions import ( - async_txsub "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/clients/stellarcore" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/support/render/problem" @@ -18,7 +18,7 @@ const ( type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - ClientWithMetrics async_txsub.ClientWithMetricsInterface + ClientWithMetrics stellarcore.ClientWithMetricsInterface CoreStateGetter } diff --git a/services/horizon/internal/integration/async_txsub_test.go b/services/horizon/internal/integration/txsub_async_test.go similarity index 100% rename from services/horizon/internal/integration/async_txsub_test.go rename to services/horizon/internal/integration/txsub_async_test.go From 1725c9137db20448f8ff5529f80ff5ca1efdd6fb Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 29 Feb 2024 11:44:20 -0500 Subject: [PATCH 15/68] Add openAPI taml --- services/horizon/internal/httpx/router.go | 9 + .../httpx/static/txsub_async_oapi.yaml | 174 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 services/horizon/internal/httpx/static/txsub_async_oapi.yaml diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 387c1f9195..c00531d3f3 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -371,6 +371,15 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate w.Header().Set("Content-Type", "application/openapi+yaml") w.Write(p) }) + r.Internal.Get("/transactions-async", func(w http.ResponseWriter, r *http.Request) { + p, err := staticFiles.ReadFile("static/txsub_async_oapi.yaml") + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/openapi+yaml") + w.Write(p) + }) r.Internal.Get("/metrics", promhttp.HandlerFor(config.PrometheusRegistry, promhttp.HandlerOpts{}).ServeHTTP) r.Internal.Get("/debug/pprof/heap", pprof.Index) r.Internal.Get("/debug/pprof/profile", pprof.Profile) diff --git a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml new file mode 100644 index 0000000000..8414121fe7 --- /dev/null +++ b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml @@ -0,0 +1,174 @@ +openapi: 3.0.0 +info: + title: Stellar Horizon Async Transaction Submission + version: "1.0" +paths: + /transactions-async: + post: + summary: Asynchronously submit a transaction to the Stellar network. + tags: + - Transactions + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + tx: + type: string + description: A base64 transaction XDR string. + required: + - tx + responses: + '201': + description: Transaction has been received by core and is in pending status. + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' + example: + tx_status: "PENDING" + status: 201 + hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" + + '400': + description: Transaction is malformed; transaction submission exception; transaction submission failed; invalid submission status from core; ERROR status from core. + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' + - $ref: '#/components/schemas/Problem' + examples: + TransactionMalformedExample: + summary: Transaction Malformed + value: + type: "transaction_malformed" + title: "Transaction Malformed" + status: 400 + detail: "Horizon could not decode the transaction envelope in this request. A transaction should be an XDR TransactionEnvelope struct encoded using base64. The envelope read from this request is echoed in the `extras.envelope_xdr` field of this response for your convenience." + extras: + envelope_xdr: "" + TransactionFailedExample: + summary: Transaction Submission Failed + value: + type: "transaction_submission_failed" + title: "Transaction Submission Failed" + status: 400 + detail: "Could not submit transaction to stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_failed" + extras: + envelope_xdr: "" + error: "Error details here" + TransactionExceptionExample: + summary: Transaction Submission Exception + value: + type: "transaction_submission_exception" + title: "Transaction Submission Exception" + status: 400 + detail: "Received exception from stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_exception" + extras: + envelope_xdr: "" + error: "Exception details here" + ErrorStatusExample: + summary: ERROR Status from core + value: + errorResultXdr: "AAAAAAAAAGT////7AAAAAA==" + diagnosticEventsXdr: "AAAAAAAAAGT////7AAAAAA==" + tx_status: "ERROR" + status: 400 + hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" + '405': + description: Transaction submission has been disabled for Horizon. + content: + application/json: + schema: + $ref: '#/components/schemas/Problem' + example: + TransactionSubmissionDisabledExample: + summary: Transaction Submission Disabled + value: + type: "transaction_submission_disabled" + title: "Transaction Submission Disabled" + status: 405 + detail: "Transaction submission has been disabled for Horizon. To enable it again, remove env variable DISABLE_TX_SUB." + extras: + envelope_xdr: "" + '409': + description: Transaction is a duplicate of a previously submitted transaction. + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' + example: + errorResultXdr: "" + diagnosticEventsXdr: "" + tx_status: "DUPLICATE" + status: 409 + hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" + '503': + description: History DB is stale; core is unavailable for transaction submission. + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' + examples: + HistoryDBStaleExample: + summary: Historical DB Is Too Stale + value: + type: "stale_history" + title: "Historical DB Is Too Stale" + status: 503 + detail: "This horizon instance is configured to reject client requests when it can determine that the history database is lagging too far behind the connected instance of Stellar-Core or read replica. It's also possible that Stellar-Core is out of sync. Please try again later." + extras: + envelope_xdr: "" + TryAgainLaterExample: + summary: TRY_AGAIN_LATER Status from core + value: + tx_status: "TRY_AGAIN_LATER" + status: 503 + hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" + + +components: + schemas: + AsyncTransactionSubmissionResponse: + type: object + properties: + errorResultXdr: + type: string + nullable: true + description: TransactionResult XDR string which is present only if the submission status from core is an ERROR. + diagnosticEventsXdr: + type: string + nullable: true + description: Base64-encoded slice of xdr.DiagnosticEvent. It is present only if the submission status from core is an ERROR. + tx_status: + type: string + enum: ["ERROR", "PENDING", "DUPLICATE", "TRY_AGAIN_LATER"] + description: Status of the transaction submission. + status: + type: integer + description: Corresponding HTTP status code. + hash: + type: string + description: Hash of the transaction. + Problem: + type: object + properties: + type: + type: string + description: Identifies the problem type. + title: + type: string + description: A short, human-readable summary of the problem type. + status: + type: integer + description: The HTTP status code for this occurrence of the problem. + detail: + type: string + description: A human-readable explanation specific to this occurrence of the problem. + extras: + type: object + additionalProperties: true + description: Additional details that might help the client understand the error(s) that occurred. From 9dfc179be9066a71d95e5161c1bf9eb906d72b3b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 11:17:32 -0500 Subject: [PATCH 16/68] Address review changes - 1 --- .../internal/actions/submit_transaction.go | 51 ++++++++++++++++ .../actions/submit_transaction_async.go | 51 ++++------------ .../actions/submit_transaction_async_test.go | 19 +++--- services/horizon/internal/actions/utils.go | 58 ------------------- 4 files changed, 72 insertions(+), 107 deletions(-) delete mode 100644 services/horizon/internal/actions/utils.go diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index 3b5ba77dca..53ab722473 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -2,6 +2,10 @@ package actions import ( "context" + "encoding/hex" + "github.com/stellar/go/network" + "github.com/stellar/go/support/errors" + "mime" "net/http" "github.com/stellar/go/protocols/horizon" @@ -25,6 +29,53 @@ type SubmitTransactionHandler struct { CoreStateGetter } +type envelopeInfo struct { + hash string + innerHash string + raw string + parsed xdr.TransactionEnvelope +} + +func extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { + result := envelopeInfo{raw: raw} + err := xdr.SafeUnmarshalBase64(raw, &result.parsed) + if err != nil { + return result, err + } + + var hash [32]byte + hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) + if err != nil { + return result, err + } + result.hash = hex.EncodeToString(hash[:]) + if result.parsed.IsFeeBump() { + hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) + if err != nil { + return result, err + } + result.innerHash = hex.EncodeToString(hash[:]) + } + return result, nil +} + +func validateBodyType(r *http.Request) error { + c := r.Header.Get("Content-Type") + if c == "" { + return nil + } + + mt, _, err := mime.ParseMediaType(c) + if err != nil { + return errors.Wrap(err, "Could not determine mime type") + } + + if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { + return &hProblem.UnsupportedMediaType + } + return nil +} + func (handler SubmitTransactionHandler) response(r *http.Request, info envelopeInfo, result txsub.Result) (hal.Pageable, error) { if result.Err == nil { var resource horizon.Transaction diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 51f8fc04cb..dc414fdcf3 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -2,18 +2,19 @@ package actions import ( "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/protocols/horizon" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/support/render/problem" "net/http" ) -const ( - HTTPStatusCodeForPending = http.StatusCreated - HTTPStatusCodeForDuplicate = http.StatusConflict - HTTPStatusCodeForTryAgainLater = http.StatusServiceUnavailable - HTTPStatusCodeForError = http.StatusBadRequest -) +var coreStatusToHTTPStatus = map[string]int{ + proto.TXStatusPending: http.StatusCreated, + proto.TXStatusDuplicate: http.StatusConflict, + proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, + proto.TXStatusError: http.StatusBadRequest, +} type AsyncSubmitTransactionHandler struct { NetworkPassphrase string @@ -22,27 +23,6 @@ type AsyncSubmitTransactionHandler struct { CoreStateGetter } -// AsyncTransactionSubmissionResponse represents the response returned by Horizon -// when using the transaction-submission-async endpoint. -type AsyncTransactionSubmissionResponse struct { - // ErrorResultXDR is present only if Status is equal to proto.TXStatusError. - // ErrorResultXDR is a TransactionResult xdr string which contains details on why - // the transaction could not be accepted by stellar-core. - ErrorResultXDR string `json:"errorResultXdr,omitempty"` - // DiagnosticEventsXDR is present only if Status is equal to proto.TXStatusError. - // DiagnosticEventsXDR is a base64-encoded slice of xdr.DiagnosticEvent - DiagnosticEventsXDR string `json:"diagnosticEventsXdr,omitempty"` - // TxStatus represents the status of the transaction submission returned by stellar-core. - // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, - // proto.TXStatusTryAgainLater, or proto.TXStatusError. - TxStatus string `json:"tx_status"` - // HttpStatus represents the corresponding http status code. - HttpStatus int `json:"status"` - // Hash is a hash of the transaction which can be used to look up whether - // the transaction was included in the ledger. - Hash string `json:"hash"` -} - func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { if err := validateBodyType(r); err != nil { return nil, err @@ -123,26 +103,17 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http switch resp.Status { case proto.TXStatusError: - return AsyncTransactionSubmissionResponse{ + return horizon.AsyncTransactionSubmissionResponse{ ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, TxStatus: resp.Status, - HttpStatus: HTTPStatusCodeForError, + HttpStatus: coreStatusToHTTPStatus[resp.Status], Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: - var httpStatus int - if resp.Status == proto.TXStatusPending { - httpStatus = HTTPStatusCodeForPending - } else if resp.Status == proto.TXStatusDuplicate { - httpStatus = HTTPStatusCodeForDuplicate - } else { - httpStatus = HTTPStatusCodeForTryAgainLater - } - - return AsyncTransactionSubmissionResponse{ + return horizon.AsyncTransactionSubmissionResponse{ TxStatus: resp.Status, - HttpStatus: httpStatus, + HttpStatus: coreStatusToHTTPStatus[resp.Status], Hash: info.hash, }, nil default: diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 5d8994a448..680d1c33d3 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -3,6 +3,7 @@ package actions import ( "context" stellarcore "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/protocols/horizon" "net/http" "net/http/httptest" "net/url" @@ -149,7 +150,7 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { successCases := []struct { mockCoreResponse *proto.TXResponse - expectedResponse AsyncTransactionSubmissionResponse + expectedResponse horizon.AsyncTransactionSubmissionResponse }{ { mockCoreResponse: &proto.TXResponse{ @@ -158,11 +159,11 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { Status: proto.TXStatusError, DiagnosticEvents: "test-diagnostic-events", }, - expectedResponse: AsyncTransactionSubmissionResponse{ + expectedResponse: horizon.AsyncTransactionSubmissionResponse{ ErrorResultXDR: "test-error", DiagnosticEventsXDR: "test-diagnostic-events", TxStatus: proto.TXStatusError, - HttpStatus: HTTPStatusCodeForError, + HttpStatus: http.StatusBadRequest, Hash: TxHash, }, }, @@ -170,9 +171,9 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusPending, }, - expectedResponse: AsyncTransactionSubmissionResponse{ + expectedResponse: horizon.AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusPending, - HttpStatus: HTTPStatusCodeForPending, + HttpStatus: http.StatusCreated, Hash: TxHash, }, }, @@ -180,9 +181,9 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusDuplicate, }, - expectedResponse: AsyncTransactionSubmissionResponse{ + expectedResponse: horizon.AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusDuplicate, - HttpStatus: HTTPStatusCodeForDuplicate, + HttpStatus: http.StatusConflict, Hash: TxHash, }, }, @@ -190,9 +191,9 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { mockCoreResponse: &proto.TXResponse{ Status: proto.TXStatusTryAgainLater, }, - expectedResponse: AsyncTransactionSubmissionResponse{ + expectedResponse: horizon.AsyncTransactionSubmissionResponse{ TxStatus: proto.TXStatusTryAgainLater, - HttpStatus: HTTPStatusCodeForTryAgainLater, + HttpStatus: http.StatusServiceUnavailable, Hash: TxHash, }, }, diff --git a/services/horizon/internal/actions/utils.go b/services/horizon/internal/actions/utils.go deleted file mode 100644 index 1ad32b5d4a..0000000000 --- a/services/horizon/internal/actions/utils.go +++ /dev/null @@ -1,58 +0,0 @@ -package actions - -import ( - "encoding/hex" - "github.com/stellar/go/network" - hProblem "github.com/stellar/go/services/horizon/internal/render/problem" - "github.com/stellar/go/support/errors" - "github.com/stellar/go/xdr" - "mime" - "net/http" -) - -type envelopeInfo struct { - hash string - innerHash string - raw string - parsed xdr.TransactionEnvelope -} - -func extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) { - result := envelopeInfo{raw: raw} - err := xdr.SafeUnmarshalBase64(raw, &result.parsed) - if err != nil { - return result, err - } - - var hash [32]byte - hash, err = network.HashTransactionInEnvelope(result.parsed, passphrase) - if err != nil { - return result, err - } - result.hash = hex.EncodeToString(hash[:]) - if result.parsed.IsFeeBump() { - hash, err = network.HashTransaction(result.parsed.FeeBump.Tx.InnerTx.V1.Tx, passphrase) - if err != nil { - return result, err - } - result.innerHash = hex.EncodeToString(hash[:]) - } - return result, nil -} - -func validateBodyType(r *http.Request) error { - c := r.Header.Get("Content-Type") - if c == "" { - return nil - } - - mt, _, err := mime.ParseMediaType(c) - if err != nil { - return errors.Wrap(err, "Could not determine mime type") - } - - if mt != "application/x-www-form-urlencoded" && mt != "multipart/form-data" { - return &hProblem.UnsupportedMediaType - } - return nil -} From 9a4e0d8773ff56805fc6e4fd79c14df5f49cb8eb Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 14:15:12 -0500 Subject: [PATCH 17/68] Remove private methods from interface --- clients/stellarcore/client.go | 6 -- clients/stellarcore/metrics_client.go | 89 +++++++++++------------ services/horizon/internal/httpx/router.go | 2 +- 3 files changed, 45 insertions(+), 52 deletions(-) diff --git a/clients/stellarcore/client.go b/clients/stellarcore/client.go index 2936d099d8..f66b455ac5 100644 --- a/clients/stellarcore/client.go +++ b/clients/stellarcore/client.go @@ -30,15 +30,9 @@ type Client struct { } type ClientInterface interface { - Upgrade(ctx context.Context, version int) (err error) - GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) Info(ctx context.Context) (resp *proto.InfoResponse, err error) - SetCursor(ctx context.Context, id string, cursor int32) (err error) SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) - WaitForNetworkSync(ctx context.Context) error ManualClose(ctx context.Context) (err error) - http() HTTP - simpleGet(ctx context.Context, newPath string, query url.Values) (*http.Request, error) } // drainReponse is a helper method for draining the body stream off the http diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index ea58d3134a..66dabafca0 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -10,32 +10,31 @@ import ( type ClientWithMetricsInterface interface { SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) - UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) } type ClientWithMetrics struct { CoreClient ClientInterface - AsyncTxSubMetrics struct { - // AsyncSubmissionDuration exposes timing metrics about the rate and latency of + TxSubMetrics struct { + // SubmissionDuration exposes timing metrics about the rate and latency of // submissions to stellar-core - AsyncSubmissionDuration *prometheus.SummaryVec + SubmissionDuration *prometheus.SummaryVec - // AsyncSubmissionsCounter tracks the rate of transactions that have + // SubmissionsCounter tracks the rate of transactions that have // been submitted to this process - AsyncSubmissionsCounter *prometheus.CounterVec + SubmissionsCounter *prometheus.CounterVec - // AsyncV0TransactionsCounter tracks the rate of v0 transaction envelopes that + // V0TransactionsCounter tracks the rate of v0 transaction envelopes that // have been submitted to this process - AsyncV0TransactionsCounter *prometheus.CounterVec + V0TransactionsCounter *prometheus.CounterVec - // AsyncV1TransactionsCounter tracks the rate of v1 transaction envelopes that + // V1TransactionsCounter tracks the rate of v1 transaction envelopes that // have been submitted to this process - AsyncV1TransactionsCounter *prometheus.CounterVec + V1TransactionsCounter *prometheus.CounterVec - // AsyncFeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that + // FeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that // have been submitted to this process - AsyncFeeBumpTransactionsCounter *prometheus.CounterVec + FeeBumpTransactionsCounter *prometheus.CounterVec } } @@ -57,74 +56,74 @@ func (c *ClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.Tr label = prometheus.Labels{"status": response.Status} } - c.AsyncTxSubMetrics.AsyncSubmissionDuration.With(label).Observe(duration) - c.AsyncTxSubMetrics.AsyncSubmissionsCounter.With(label).Inc() + c.TxSubMetrics.SubmissionDuration.With(label).Observe(duration) + c.TxSubMetrics.SubmissionsCounter.With(label).Inc() switch envelope.Type { case xdr.EnvelopeTypeEnvelopeTypeTxV0: - c.AsyncTxSubMetrics.AsyncV0TransactionsCounter.With(label).Inc() + c.TxSubMetrics.V0TransactionsCounter.With(label).Inc() case xdr.EnvelopeTypeEnvelopeTypeTx: - c.AsyncTxSubMetrics.AsyncV1TransactionsCounter.With(label).Inc() + c.TxSubMetrics.V1TransactionsCounter.With(label).Inc() case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: - c.AsyncTxSubMetrics.AsyncFeeBumpTransactionsCounter.With(label).Inc() + c.TxSubMetrics.FeeBumpTransactionsCounter.With(label).Inc() } } -func NewClientWithMetrics(client Client, registry *prometheus.Registry) *ClientWithMetrics { - asyncSubmissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ +func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) *ClientWithMetrics { + submissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: "horizon", - Subsystem: "async_txsub", + Subsystem: prometheusSubsystem, Name: "submission_duration_seconds", Help: "submission durations to Stellar-Core, sliding window = 10m", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{"status"}) - asyncSubmissionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + submissionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "horizon", - Subsystem: "async_txsub", + Subsystem: prometheusSubsystem, Name: "submissions_count", - Help: "number of submissions using the async txsub endpoint, sliding window = 10m", + Help: "number of submissions, sliding window = 10m", }, []string{"status"}) - asyncV0TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + v0TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "horizon", - Subsystem: "async_txsub", + Subsystem: prometheusSubsystem, Name: "v0_count", Help: "number of v0 transaction envelopes submitted, sliding window = 10m", }, []string{"status"}) - asyncV1TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + v1TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "horizon", - Subsystem: "async_txsub", + Subsystem: prometheusSubsystem, Name: "v1_count", Help: "number of v1 transaction envelopes submitted, sliding window = 10m", }, []string{"status"}) - asyncFeeBumpTransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ + feeBumpTransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "horizon", - Subsystem: "async_txsub", + Subsystem: prometheusSubsystem, Name: "feebump_count", Help: "number of fee bump transaction envelopes submitted, sliding window = 10m", }, []string{"status"}) registry.MustRegister( - asyncSubmissionDuration, - asyncSubmissionsCounter, - asyncV0TransactionsCounter, - asyncV1TransactionsCounter, - asyncFeeBumpTransactionsCounter, + submissionDuration, + submissionsCounter, + v0TransactionsCounter, + v1TransactionsCounter, + feeBumpTransactionsCounter, ) return &ClientWithMetrics{ CoreClient: &client, - AsyncTxSubMetrics: struct { - AsyncSubmissionDuration *prometheus.SummaryVec - AsyncSubmissionsCounter *prometheus.CounterVec - AsyncV0TransactionsCounter *prometheus.CounterVec - AsyncV1TransactionsCounter *prometheus.CounterVec - AsyncFeeBumpTransactionsCounter *prometheus.CounterVec + TxSubMetrics: struct { + SubmissionDuration *prometheus.SummaryVec + SubmissionsCounter *prometheus.CounterVec + V0TransactionsCounter *prometheus.CounterVec + V1TransactionsCounter *prometheus.CounterVec + FeeBumpTransactionsCounter *prometheus.CounterVec }{ - AsyncSubmissionDuration: asyncSubmissionDuration, - AsyncSubmissionsCounter: asyncSubmissionsCounter, - AsyncV0TransactionsCounter: asyncV0TransactionsCounter, - AsyncV1TransactionsCounter: asyncV1TransactionsCounter, - AsyncFeeBumpTransactionsCounter: asyncFeeBumpTransactionsCounter, + SubmissionDuration: submissionDuration, + SubmissionsCounter: submissionsCounter, + V0TransactionsCounter: v0TransactionsCounter, + V1TransactionsCounter: v1TransactionsCounter, + FeeBumpTransactionsCounter: feeBumpTransactionsCounter, }, } } diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index c00531d3f3..7bed6abd4e 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -341,7 +341,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate ClientWithMetrics: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: http.DefaultClient, URL: config.StellarCoreURL, - }, config.PrometheusRegistry), + }, config.PrometheusRegistry, "async_txsub"), }}) // Network state related endpoints From 2b97e2d6f4ee9595f8e8dc25c21e0c4d6a4f16a8 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 14:35:45 -0500 Subject: [PATCH 18/68] Use common metrics client for legacy and async txsub --- services/horizon/internal/init.go | 2 +- services/horizon/internal/txsub/main.go | 2 +- services/horizon/internal/txsub/submitter.go | 21 +++++--- services/horizon/internal/txsub/system.go | 54 ++------------------ 4 files changed, 20 insertions(+), 59 deletions(-) diff --git a/services/horizon/internal/init.go b/services/horizon/internal/init.go index 0c0fe2c2cd..673b203508 100644 --- a/services/horizon/internal/init.go +++ b/services/horizon/internal/init.go @@ -232,7 +232,7 @@ func initWebMetrics(app *App) { func initSubmissionSystem(app *App) { app.submitter = &txsub.System{ Pending: txsub.NewDefaultSubmissionList(), - Submitter: txsub.NewDefaultSubmitter(http.DefaultClient, app.config.StellarCoreURL), + Submitter: txsub.NewDefaultSubmitter(http.DefaultClient, app.config.StellarCoreURL, app.prometheusRegistry), DB: func(ctx context.Context) txsub.HorizonDB { return &history.Q{SessionInterface: app.HorizonSession()} }, diff --git a/services/horizon/internal/txsub/main.go b/services/horizon/internal/txsub/main.go index b466fc0055..d998d150a2 100644 --- a/services/horizon/internal/txsub/main.go +++ b/services/horizon/internal/txsub/main.go @@ -42,7 +42,7 @@ type OpenSubmissionList interface { // provider. type Submitter interface { // Submit sends the provided transaction envelope to stellar-core - Submit(context.Context, string) SubmissionResult + Submit(context.Context, string, xdr.TransactionEnvelope) SubmissionResult } // Result represents the response from a ResultProvider. Given no diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index 27ce85c87a..bc154b5f1e 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -2,6 +2,8 @@ package txsub import ( "context" + "github.com/prometheus/client_golang/prometheus" + "github.com/stellar/go/xdr" "net/http" "time" @@ -14,12 +16,19 @@ import ( // NewDefaultSubmitter returns a new, simple Submitter implementation // that submits directly to the stellar-core at `url` using the http client // `h`. -func NewDefaultSubmitter(h *http.Client, url string) Submitter { +func NewDefaultSubmitter(h *http.Client, url string, registry *prometheus.Registry) Submitter { + //return &submitter{ + // StellarCore: &stellarcore.Client{ + // HTTP: h, + // URL: url, + // }, + // Log: log.DefaultLogger.WithField("service", "txsub.submitter"), + //} return &submitter{ - StellarCore: &stellarcore.Client{ + StellarCore: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: h, URL: url, - }, + }, registry, "txsub"), Log: log.DefaultLogger.WithField("service", "txsub.submitter"), } } @@ -28,13 +37,13 @@ func NewDefaultSubmitter(h *http.Client, url string) Submitter { // submits directly to the configured stellar-core instance using the // configured http client. type submitter struct { - StellarCore *stellarcore.Client + StellarCore stellarcore.ClientWithMetricsInterface Log *log.Entry } // Submit sends the provided envelope to stellar-core and parses the response into // a SubmissionResult -func (sub *submitter) Submit(ctx context.Context, env string) (result SubmissionResult) { +func (sub *submitter) Submit(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (result SubmissionResult) { start := time.Now() defer func() { result.Duration = time.Since(start) @@ -44,7 +53,7 @@ func (sub *submitter) Submit(ctx context.Context, env string) (result Submission }).Info("Submitter result") }() - cresp, err := sub.StellarCore.SubmitTransaction(ctx, env) + cresp, err := sub.StellarCore.SubmitTransaction(ctx, rawTx, envelope) if err != nil { result.Err = errors.Wrap(err, "failed to submit") return diff --git a/services/horizon/internal/txsub/system.go b/services/horizon/internal/txsub/system.go index 31038135f3..57846eb091 100644 --- a/services/horizon/internal/txsub/system.go +++ b/services/horizon/internal/txsub/system.go @@ -44,10 +44,6 @@ type System struct { LedgerState ledger.StateInterface Metrics struct { - // SubmissionDuration exposes timing metrics about the rate and latency of - // submissions to stellar-core - SubmissionDuration prometheus.Summary - // OpenSubmissionsGauge tracks the count of "open" submissions (i.e. // submissions whose transactions haven't been confirmed successful or failed OpenSubmissionsGauge prometheus.Gauge @@ -59,30 +55,14 @@ type System struct { // SuccessfulSubmissionsCounter tracks the rate of successful transactions that // have been submitted to this process SuccessfulSubmissionsCounter prometheus.Counter - - // V0TransactionsCounter tracks the rate of v0 transaction envelopes that - // have been submitted to this process - V0TransactionsCounter prometheus.Counter - - // V1TransactionsCounter tracks the rate of v1 transaction envelopes that - // have been submitted to this process - V1TransactionsCounter prometheus.Counter - - // FeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that - // have been submitted to this process - FeeBumpTransactionsCounter prometheus.Counter } } // RegisterMetrics registers the prometheus metrics func (sys *System) RegisterMetrics(registry *prometheus.Registry) { - registry.MustRegister(sys.Metrics.SubmissionDuration) registry.MustRegister(sys.Metrics.OpenSubmissionsGauge) registry.MustRegister(sys.Metrics.FailedSubmissionsCounter) registry.MustRegister(sys.Metrics.SuccessfulSubmissionsCounter) - registry.MustRegister(sys.Metrics.V0TransactionsCounter) - registry.MustRegister(sys.Metrics.V1TransactionsCounter) - registry.MustRegister(sys.Metrics.FeeBumpTransactionsCounter) } // Submit submits the provided base64 encoded transaction envelope to the @@ -129,8 +109,7 @@ func (sys *System) Submit( return } - sr := sys.submitOnce(ctx, rawTx) - sys.updateTransactionTypeMetrics(envelope) + sr := sys.submitOnce(ctx, rawTx, envelope) if sr.Err != nil { // any error other than "txBAD_SEQ" is a failure @@ -222,12 +201,10 @@ func (sys *System) deriveTxSubError(ctx context.Context) error { // Submit submits the provided base64 encoded transaction envelope to the // network using this submission system. -func (sys *System) submitOnce(ctx context.Context, env string) SubmissionResult { +func (sys *System) submitOnce(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) SubmissionResult { // submit to stellar-core - sr := sys.Submitter.Submit(ctx, env) - sys.Metrics.SubmissionDuration.Observe(float64(sr.Duration.Seconds())) + sr := sys.Submitter.Submit(ctx, rawTx, envelope) - // if received or duplicate, add to the open submissions list if sr.Err == nil { sys.Metrics.SuccessfulSubmissionsCounter.Inc() } else { @@ -237,17 +214,6 @@ func (sys *System) submitOnce(ctx context.Context, env string) SubmissionResult return sr } -func (sys *System) updateTransactionTypeMetrics(envelope xdr.TransactionEnvelope) { - switch envelope.Type { - case xdr.EnvelopeTypeEnvelopeTypeTxV0: - sys.Metrics.V0TransactionsCounter.Inc() - case xdr.EnvelopeTypeEnvelopeTypeTx: - sys.Metrics.V1TransactionsCounter.Inc() - case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: - sys.Metrics.FeeBumpTransactionsCounter.Inc() - } -} - // setTickInProgress sets `tickInProgress` to `true` if it's // `false`. Returns `true` if `tickInProgress` has been switched // to `true` inside this method and `Tick()` should continue. @@ -360,11 +326,6 @@ func (sys *System) Init() { sys.initializer.Do(func() { sys.Log = log.DefaultLogger.WithField("service", "txsub.System") - sys.Metrics.SubmissionDuration = prometheus.NewSummary(prometheus.SummaryOpts{ - Namespace: "horizon", Subsystem: "txsub", Name: "submission_duration_seconds", - Help: "submission durations to Stellar-Core, sliding window = 10m", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }) sys.Metrics.FailedSubmissionsCounter = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: "horizon", Subsystem: "txsub", Name: "failed", }) @@ -374,15 +335,6 @@ func (sys *System) Init() { sys.Metrics.OpenSubmissionsGauge = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: "horizon", Subsystem: "txsub", Name: "open", }) - sys.Metrics.V0TransactionsCounter = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: "horizon", Subsystem: "txsub", Name: "v0", - }) - sys.Metrics.V1TransactionsCounter = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: "horizon", Subsystem: "txsub", Name: "v1", - }) - sys.Metrics.FeeBumpTransactionsCounter = prometheus.NewCounter(prometheus.CounterOpts{ - Namespace: "horizon", Subsystem: "txsub", Name: "feebump", - }) sys.accountSeqPollInterval = time.Second From 1edb2b0f26919a1fe2baf132190f754acb647d2d Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 14:57:24 -0500 Subject: [PATCH 19/68] Fix submitter test --- .../horizon/internal/txsub/helpers_test.go | 3 +- .../horizon/internal/txsub/submitter_test.go | 38 ++++++++++--------- .../horizon/internal/txsub/system_test.go | 2 - 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/services/horizon/internal/txsub/helpers_test.go b/services/horizon/internal/txsub/helpers_test.go index 3c4cb6cb0b..ecc26d6ec8 100644 --- a/services/horizon/internal/txsub/helpers_test.go +++ b/services/horizon/internal/txsub/helpers_test.go @@ -11,6 +11,7 @@ import ( "database/sql" "github.com/prometheus/client_golang/prometheus" "github.com/stellar/go/services/horizon/internal/ledger" + "github.com/stellar/go/xdr" "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stretchr/testify/mock" @@ -23,7 +24,7 @@ type MockSubmitter struct { } // Submit implements `txsub.Submitter` -func (sub *MockSubmitter) Submit(ctx context.Context, env string) SubmissionResult { +func (sub *MockSubmitter) Submit(ctx context.Context, env string, envelope xdr.TransactionEnvelope) SubmissionResult { sub.WasSubmittedTo = true return sub.R } diff --git a/services/horizon/internal/txsub/submitter_test.go b/services/horizon/internal/txsub/submitter_test.go index 4406f46fb8..1f00bc21a1 100644 --- a/services/horizon/internal/txsub/submitter_test.go +++ b/services/horizon/internal/txsub/submitter_test.go @@ -1,6 +1,8 @@ package txsub import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/stellar/go/xdr" "github.com/stretchr/testify/assert" "net/http" "testing" @@ -17,8 +19,8 @@ func TestDefaultSubmitter(t *testing.T) { }`) defer server.Close() - s := NewDefaultSubmitter(http.DefaultClient, server.URL) - sr := s.Submit(ctx, "hello") + s := NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr := s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.Nil(t, sr.Err) assert.True(t, sr.Duration > 0) assert.Equal(t, "hello", server.LastRequest.URL.Query().Get("blob")) @@ -30,41 +32,41 @@ func TestDefaultSubmitter(t *testing.T) { }`) defer server.Close() - s = NewDefaultSubmitter(http.DefaultClient, server.URL) - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.Nil(t, sr.Err) // Errors when the stellar-core url is empty - s = NewDefaultSubmitter(http.DefaultClient, "") - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, "", prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) //errors when the stellar-core url is not parseable - s = NewDefaultSubmitter(http.DefaultClient, "http://Not a url") - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, "http://Not a url", prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) // errors when the stellar-core url is not reachable - s = NewDefaultSubmitter(http.DefaultClient, "http://127.0.0.1:65535") - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, "http://127.0.0.1:65535", prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) // errors when the stellar-core returns an unparseable response server = test.NewStaticMockServer(`{`) defer server.Close() - s = NewDefaultSubmitter(http.DefaultClient, server.URL) - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) // errors when the stellar-core returns an exception response server = test.NewStaticMockServer(`{"exception": "Invalid XDR"}`) defer server.Close() - s = NewDefaultSubmitter(http.DefaultClient, server.URL) - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "Invalid XDR") @@ -72,8 +74,8 @@ func TestDefaultSubmitter(t *testing.T) { server = test.NewStaticMockServer(`{"status": "NOTREAL"}`) defer server.Close() - s = NewDefaultSubmitter(http.DefaultClient, server.URL) - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "NOTREAL") @@ -81,8 +83,8 @@ func TestDefaultSubmitter(t *testing.T) { server = test.NewStaticMockServer(`{"status": "ERROR", "error": "1234"}`) defer server.Close() - s = NewDefaultSubmitter(http.DefaultClient, server.URL) - sr = s.Submit(ctx, "hello") + s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) + sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) assert.IsType(t, &FailedTransactionError{}, sr.Err) ferr := sr.Err.(*FailedTransactionError) assert.Equal(t, "1234", ferr.ResultXDR) diff --git a/services/horizon/internal/txsub/system_test.go b/services/horizon/internal/txsub/system_test.go index b4a36fb522..fbaa353c1b 100644 --- a/services/horizon/internal/txsub/system_test.go +++ b/services/horizon/internal/txsub/system_test.go @@ -250,7 +250,6 @@ func (suite *SystemTestSuite) TestSubmit_NotFoundError() { assert.True(suite.T(), suite.submitter.WasSubmittedTo) assert.Equal(suite.T(), float64(0), getMetricValue(suite.system.Metrics.SuccessfulSubmissionsCounter).GetCounter().GetValue()) assert.Equal(suite.T(), float64(1), getMetricValue(suite.system.Metrics.FailedSubmissionsCounter).GetCounter().GetValue()) - assert.Equal(suite.T(), uint64(1), getMetricValue(suite.system.Metrics.SubmissionDuration).GetSummary().GetSampleCount()) } // If the error is bad_seq and the result at the transaction's sequence number is for the same hash, return result. @@ -408,7 +407,6 @@ func (suite *SystemTestSuite) TestSubmit_OpenTransactionList() { assert.Equal(suite.T(), suite.successTx.Transaction.TransactionHash, pending[0]) assert.Equal(suite.T(), float64(1), getMetricValue(suite.system.Metrics.SuccessfulSubmissionsCounter).GetCounter().GetValue()) assert.Equal(suite.T(), float64(0), getMetricValue(suite.system.Metrics.FailedSubmissionsCounter).GetCounter().GetValue()) - assert.Equal(suite.T(), uint64(1), getMetricValue(suite.system.Metrics.SubmissionDuration).GetSummary().GetSampleCount()) } // Tick should be a no-op if there are no open submissions. From fd03686d454d5a78ffe5d437bf69105944bd49b4 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 15:18:09 -0500 Subject: [PATCH 20/68] Update submit_transaction_async.go --- services/horizon/internal/actions/submit_transaction_async.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index dc414fdcf3..2c96575218 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -73,7 +73,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, &problem.P{ Type: "transaction_submission_failed", Title: "Transaction Submission Failed", - Status: http.StatusBadRequest, + Status: http.StatusInternalServerError, Detail: "Could not submit transaction to stellar-core. " + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + @@ -89,7 +89,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, &problem.P{ Type: "transaction_submission_exception", Title: "Transaction Submission Exception", - Status: http.StatusBadRequest, + Status: http.StatusInternalServerError, Detail: "Received exception from stellar-core." + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + From b95d57c2627c0256f725303c9072d93f5ed16fd7 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 15:28:42 -0500 Subject: [PATCH 21/68] Fix failing test --- .../horizon/internal/actions/submit_transaction_async_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 680d1c33d3..600e2d25a9 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -109,7 +109,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) assert.Equal(t, "transaction_submission_failed", p.Type) - assert.Equal(t, http.StatusBadRequest, p.Status) + assert.Equal(t, http.StatusInternalServerError, p.Status) } func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing.T) { @@ -138,7 +138,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) assert.Equal(t, "transaction_submission_exception", p.Type) - assert.Equal(t, http.StatusBadRequest, p.Status) + assert.Equal(t, http.StatusInternalServerError, p.Status) } func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { From 0e4142aabf19d5b25028b38c08350b268c73b85b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 4 Mar 2024 15:28:47 -0500 Subject: [PATCH 22/68] Update txsub_async_oapi.yaml --- .../httpx/static/txsub_async_oapi.yaml | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml index 8414121fe7..480bfb18ae 100644 --- a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml +++ b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml @@ -50,26 +50,6 @@ paths: detail: "Horizon could not decode the transaction envelope in this request. A transaction should be an XDR TransactionEnvelope struct encoded using base64. The envelope read from this request is echoed in the `extras.envelope_xdr` field of this response for your convenience." extras: envelope_xdr: "" - TransactionFailedExample: - summary: Transaction Submission Failed - value: - type: "transaction_submission_failed" - title: "Transaction Submission Failed" - status: 400 - detail: "Could not submit transaction to stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_failed" - extras: - envelope_xdr: "" - error: "Error details here" - TransactionExceptionExample: - summary: Transaction Submission Exception - value: - type: "transaction_submission_exception" - title: "Transaction Submission Exception" - status: 400 - detail: "Received exception from stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_exception" - extras: - envelope_xdr: "" - error: "Exception details here" ErrorStatusExample: summary: ERROR Status from core value: @@ -106,6 +86,33 @@ paths: tx_status: "DUPLICATE" status: 409 hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" + '500': + description: Transaction is a duplicate of a previously submitted transaction. + content: + application/json: + schema: + $ref: '#/components/schemas/Problem' + examples: + TransactionFailedExample: + summary: Transaction Submission Failed + value: + type: "transaction_submission_failed" + title: "Transaction Submission Failed" + status: 500 + detail: "Could not submit transaction to stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_failed" + extras: + envelope_xdr: "" + error: "Error details here" + TransactionExceptionExample: + summary: Transaction Submission Exception + value: + type: "transaction_submission_exception" + title: "Transaction Submission Exception" + status: 500 + detail: "Received exception from stellar-core. The `extras.error` field on this response contains further details. Descriptions of each code can be found at: https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-submission-async/transaction_submission_exception" + extras: + envelope_xdr: "" + error: "Exception details here" '503': description: History DB is stale; core is unavailable for transaction submission. content: From b879bd48a9126fb27ed409f600dcaa804474b079 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 5 Mar 2024 09:23:50 -0500 Subject: [PATCH 23/68] Update submitter.go --- services/horizon/internal/txsub/submitter.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index bc154b5f1e..9914fcec10 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -17,13 +17,6 @@ import ( // that submits directly to the stellar-core at `url` using the http client // `h`. func NewDefaultSubmitter(h *http.Client, url string, registry *prometheus.Registry) Submitter { - //return &submitter{ - // StellarCore: &stellarcore.Client{ - // HTTP: h, - // URL: url, - // }, - // Log: log.DefaultLogger.WithField("service", "txsub.submitter"), - //} return &submitter{ StellarCore: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: h, From d1a4eb98ef1cc36e61a96463a374e262054a2a97 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 5 Mar 2024 14:40:08 -0500 Subject: [PATCH 24/68] Interface method change --- clients/stellarcore/client.go | 4 ++++ clients/stellarcore/metrics_client.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/stellarcore/client.go b/clients/stellarcore/client.go index f66b455ac5..3e5a9fb9e9 100644 --- a/clients/stellarcore/client.go +++ b/clients/stellarcore/client.go @@ -30,8 +30,12 @@ type Client struct { } type ClientInterface interface { + Upgrade(ctx context.Context, version int) (err error) + GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) Info(ctx context.Context) (resp *proto.InfoResponse, err error) + SetCursor(ctx context.Context, id string, cursor int32) (err error) SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) + WaitForNetworkSync(ctx context.Context) error ManualClose(ctx context.Context) (err error) } diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 66dabafca0..855f37c2a9 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -41,12 +41,12 @@ type ClientWithMetrics struct { func (c *ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { startTime := time.Now() response, err := c.CoreClient.SubmitTransaction(ctx, rawTx) - c.UpdateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) + c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) return response, err } -func (c *ClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { +func (c *ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { var label prometheus.Labels if err != nil { label = prometheus.Labels{"status": "request_error"} From 085352fc63bed41479533a935f633b1ced396e1d Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 6 Mar 2024 15:09:41 -0500 Subject: [PATCH 25/68] Remove duplicate code --- clients/horizonclient/client.go | 78 ++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/clients/horizonclient/client.go b/clients/horizonclient/client.go index 40638f977f..5a7d3cd3db 100644 --- a/clients/horizonclient/client.go +++ b/clients/horizonclient/client.go @@ -440,6 +440,44 @@ func (c *Client) OperationDetail(id string) (ops operations.Operation, err error return ops, nil } +// validateFeeBumpTx checks if the inner transaction has a memo or not and converts the transaction object to +// base64 string. +func (c *Client) validateFeeBumpTx(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (string, error) { + var err error + if inner := transaction.InnerTransaction(); !opts.SkipMemoRequiredCheck && inner.Memo() == nil { + err = c.checkMemoRequired(inner) + if err != nil { + return "", err + } + } + + txeBase64, err := transaction.Base64() + if err != nil { + err = errors.Wrap(err, "Unable to convert transaction object to base64 string") + return "", err + } + return txeBase64, nil +} + +// validateTx checks if the transaction has a memo or not and converts the transaction object to +// base64 string. +func (c *Client) validateTx(transaction *txnbuild.Transaction, opts SubmitTxOpts) (string, error) { + var err error + if !opts.SkipMemoRequiredCheck && transaction.Memo() == nil { + err = c.checkMemoRequired(transaction) + if err != nil { + return "", err + } + } + + txeBase64, err := transaction.Base64() + if err != nil { + err = errors.Wrap(err, "Unable to convert transaction object to base64 string") + return "", err + } + return txeBase64, nil +} + // SubmitTransactionXDR submits a transaction represented as a base64 XDR string to the network. err can be either error object or horizon.Error object. // See https://developers.stellar.org/api/resources/transactions/post/ func (c *Client) SubmitTransactionXDR(transactionXdr string) (tx hProtocol.Transaction, @@ -469,16 +507,8 @@ func (c *Client) SubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransacti func (c *Client) SubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (tx hProtocol.Transaction, err error) { // only check if memo is required if skip is false and the inner transaction // doesn't have a memo. - if inner := transaction.InnerTransaction(); !opts.SkipMemoRequiredCheck && inner.Memo() == nil { - err = c.checkMemoRequired(inner) - if err != nil { - return - } - } - - txeBase64, err := transaction.Base64() + txeBase64, err := c.validateFeeBumpTx(transaction, opts) if err != nil { - err = errors.Wrap(err, "Unable to convert transaction object to base64 string") return } @@ -505,16 +535,8 @@ func (c *Client) SubmitTransaction(transaction *txnbuild.Transaction) (tx hProto func (c *Client) SubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (tx hProtocol.Transaction, err error) { // only check if memo is required if skip is false and the transaction // doesn't have a memo. - if !opts.SkipMemoRequiredCheck && transaction.Memo() == nil { - err = c.checkMemoRequired(transaction) - if err != nil { - return - } - } - - txeBase64, err := transaction.Base64() + txeBase64, err := c.validateTx(transaction, opts) if err != nil { - err = errors.Wrap(err, "Unable to convert transaction object to base64 string") return } @@ -545,16 +567,8 @@ func (c *Client) AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTran func (c *Client) AsyncSubmitFeeBumpTransactionWithOptions(transaction *txnbuild.FeeBumpTransaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { // only check if memo is required if skip is false and the inner transaction // doesn't have a memo. - if inner := transaction.InnerTransaction(); !opts.SkipMemoRequiredCheck && inner.Memo() == nil { - err = c.checkMemoRequired(inner) - if err != nil { - return - } - } - - txeBase64, err := transaction.Base64() + txeBase64, err := c.validateFeeBumpTx(transaction, opts) if err != nil { - err = errors.Wrap(err, "Unable to convert transaction object to base64 string") return } @@ -577,16 +591,8 @@ func (c *Client) AsyncSubmitTransaction(transaction *txnbuild.Transaction) (txRe func (c *Client) AsyncSubmitTransactionWithOptions(transaction *txnbuild.Transaction, opts SubmitTxOpts) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { // only check if memo is required if skip is false and the transaction // doesn't have a memo. - if !opts.SkipMemoRequiredCheck && transaction.Memo() == nil { - err = c.checkMemoRequired(transaction) - if err != nil { - return - } - } - - txeBase64, err := transaction.Base64() + txeBase64, err := c.validateTx(transaction, opts) if err != nil { - err = errors.Wrap(err, "Unable to convert transaction object to base64 string") return } From 2ba210d5e2dfabfe26cc5e85fa54f2ba405243d8 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 6 Mar 2024 16:57:57 -0500 Subject: [PATCH 26/68] Add test for GET /transactions-async --- .../internal/integration/txsub_async_test.go | 16 ++++++++++++++++ .../internal/test/integration/integration.go | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/services/horizon/internal/integration/txsub_async_test.go b/services/horizon/internal/integration/txsub_async_test.go index d8ed79b69d..e0e55c06ff 100644 --- a/services/horizon/internal/integration/txsub_async_test.go +++ b/services/horizon/internal/integration/txsub_async_test.go @@ -5,6 +5,8 @@ import ( "github.com/stellar/go/services/horizon/internal/test/integration" "github.com/stellar/go/txnbuild" "github.com/stretchr/testify/assert" + "io" + "net/http" "testing" ) @@ -116,3 +118,17 @@ func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) { Hash: "d5eb72a4c1832b89965850fff0bd9bba4b6ca102e7c89099dcaba5e7d7d2e049", }) } + +func TestAsyncTxSub_GetOpenAPISpecResponse(t *testing.T) { + itest := integration.NewTest(t, integration.Config{}) + res, err := http.Get(itest.AsyncTxSubOpenAPISpecURL()) + assert.NoError(t, err) + assert.Equal(t, res.StatusCode, 200) + + bytes, err := io.ReadAll(res.Body) + res.Body.Close() + assert.NoError(t, err) + + openAPISpec := string(bytes) + assert.Contains(t, openAPISpec, "openapi: 3.0.0") +} diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 6a6a27cc44..57bdfd99ef 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -916,6 +916,11 @@ func (i *Test) MetricsURL() string { return fmt.Sprintf("http://localhost:%d/metrics", i.AdminPort()) } +// AsyncTxSubOpenAPISpecURL returns the URL for getting the openAPI spec yaml for async-txsub endpoint. +func (i *Test) AsyncTxSubOpenAPISpecURL() string { + return fmt.Sprintf("http://localhost:%d/transactions-async", i.AdminPort()) +} + // Master returns a keypair of the network masterKey account. func (i *Test) Master() *keypair.Full { if i.masterKey != nil { From d331e41858939d73febae1f31cc3a232d5a8977e Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 6 Mar 2024 17:07:23 -0500 Subject: [PATCH 27/68] Encapsulation - 1 --- clients/stellarcore/metrics_client.go | 12 ++++++------ .../internal/actions/submit_transaction_async.go | 2 +- services/horizon/internal/txsub/submitter.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 855f37c2a9..fa20f8a1b5 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -8,11 +8,11 @@ import ( "time" ) -type ClientWithMetricsInterface interface { +type ClientWithMetrics interface { SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) } -type ClientWithMetrics struct { +type clientWithMetrics struct { CoreClient ClientInterface TxSubMetrics struct { @@ -38,7 +38,7 @@ type ClientWithMetrics struct { } } -func (c *ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { +func (c *clientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { startTime := time.Now() response, err := c.CoreClient.SubmitTransaction(ctx, rawTx) c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) @@ -46,7 +46,7 @@ func (c *ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, return response, err } -func (c *ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { +func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { var label prometheus.Labels if err != nil { label = prometheus.Labels{"status": "request_error"} @@ -69,7 +69,7 @@ func (c *ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.Tr } } -func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) *ClientWithMetrics { +func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { submissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: "horizon", Subsystem: prometheusSubsystem, @@ -110,7 +110,7 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe feeBumpTransactionsCounter, ) - return &ClientWithMetrics{ + return &clientWithMetrics{ CoreClient: &client, TxSubMetrics: struct { SubmissionDuration *prometheus.SummaryVec diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 2c96575218..21ac93f933 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -19,7 +19,7 @@ var coreStatusToHTTPStatus = map[string]int{ type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - ClientWithMetrics stellarcore.ClientWithMetricsInterface + ClientWithMetrics stellarcore.ClientWithMetrics CoreStateGetter } diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index 9914fcec10..1ac78754a2 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -30,7 +30,7 @@ func NewDefaultSubmitter(h *http.Client, url string, registry *prometheus.Regist // submits directly to the configured stellar-core instance using the // configured http client. type submitter struct { - StellarCore stellarcore.ClientWithMetricsInterface + StellarCore stellarcore.ClientWithMetrics Log *log.Entry } From a096b5f5961ba7b10165b7fcb9cc007a1e442064 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 7 Mar 2024 10:28:35 -0500 Subject: [PATCH 28/68] Change endpoint naming --- clients/horizonclient/client.go | 4 ++-- services/horizon/internal/httpx/router.go | 4 ++-- services/horizon/internal/httpx/static/txsub_async_oapi.yaml | 2 +- services/horizon/internal/test/integration/integration.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/horizonclient/client.go b/clients/horizonclient/client.go index 5a7d3cd3db..0c580de771 100644 --- a/clients/horizonclient/client.go +++ b/clients/horizonclient/client.go @@ -543,10 +543,10 @@ func (c *Client) SubmitTransactionWithOptions(transaction *txnbuild.Transaction, return c.SubmitTransactionXDR(txeBase64) } -// AsyncSubmitTransactionXDR submits a base64 XDR transaction using the transactions-async endpoint. err can be either error object or horizon.Error object. +// AsyncSubmitTransactionXDR submits a base64 XDR transaction using the transactions_async endpoint. err can be either error object or horizon.Error object. func (c *Client) AsyncSubmitTransactionXDR(transactionXdr string) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) { - request := submitRequest{endpoint: "transactions-async", transactionXdr: transactionXdr} + request := submitRequest{endpoint: "transactions_async", transactionXdr: transactionXdr} err = c.sendRequest(request, &txResp) return } diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 7bed6abd4e..63c70920ab 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -334,7 +334,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate }}) // Async Transaction submission API - r.Method(http.MethodPost, "/transactions-async", ObjectActionHandler{actions.AsyncSubmitTransactionHandler{ + r.Method(http.MethodPost, "/transactions_async", ObjectActionHandler{actions.AsyncSubmitTransactionHandler{ NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, @@ -371,7 +371,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate w.Header().Set("Content-Type", "application/openapi+yaml") w.Write(p) }) - r.Internal.Get("/transactions-async", func(w http.ResponseWriter, r *http.Request) { + r.Internal.Get("/transactions_async", func(w http.ResponseWriter, r *http.Request) { p, err := staticFiles.ReadFile("static/txsub_async_oapi.yaml") if err != nil { w.WriteHeader(http.StatusNotFound) diff --git a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml index 480bfb18ae..b90b30f302 100644 --- a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml +++ b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml @@ -3,7 +3,7 @@ info: title: Stellar Horizon Async Transaction Submission version: "1.0" paths: - /transactions-async: + /transactions_async: post: summary: Asynchronously submit a transaction to the Stellar network. tags: diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index 57bdfd99ef..d1aa3e68f7 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -918,7 +918,7 @@ func (i *Test) MetricsURL() string { // AsyncTxSubOpenAPISpecURL returns the URL for getting the openAPI spec yaml for async-txsub endpoint. func (i *Test) AsyncTxSubOpenAPISpecURL() string { - return fmt.Sprintf("http://localhost:%d/transactions-async", i.AdminPort()) + return fmt.Sprintf("http://localhost:%d/transactions_async", i.AdminPort()) } // Master returns a keypair of the network masterKey account. From 63ce1b9c134ef66d0d5dd2bdfd7e3c9a5206a182 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 7 Mar 2024 14:21:50 -0500 Subject: [PATCH 29/68] Pass interface instead of client --- clients/stellarcore/metrics_client.go | 4 ++-- services/horizon/internal/httpx/router.go | 2 +- services/horizon/internal/txsub/submitter.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index fa20f8a1b5..02ac9e5290 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -69,7 +69,7 @@ func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.Tr } } -func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { +func NewClientWithMetrics(client ClientInterface, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { submissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: "horizon", Subsystem: prometheusSubsystem, @@ -111,7 +111,7 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe ) return &clientWithMetrics{ - CoreClient: &client, + CoreClient: client, TxSubMetrics: struct { SubmissionDuration *prometheus.SummaryVec SubmissionsCounter *prometheus.CounterVec diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 63c70920ab..b5fd9af654 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -338,7 +338,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, - ClientWithMetrics: stellarcore.NewClientWithMetrics(stellarcore.Client{ + ClientWithMetrics: stellarcore.NewClientWithMetrics(&stellarcore.Client{ HTTP: http.DefaultClient, URL: config.StellarCoreURL, }, config.PrometheusRegistry, "async_txsub"), diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index 1ac78754a2..4f2ed13978 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -18,7 +18,7 @@ import ( // `h`. func NewDefaultSubmitter(h *http.Client, url string, registry *prometheus.Registry) Submitter { return &submitter{ - StellarCore: stellarcore.NewClientWithMetrics(stellarcore.Client{ + StellarCore: stellarcore.NewClientWithMetrics(&stellarcore.Client{ HTTP: h, URL: url, }, registry, "txsub"), From 0f14793ac948247dfa36f26fcc0bc9d234a34b25 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 7 Mar 2024 14:25:51 -0500 Subject: [PATCH 30/68] Remove ClientInterface --- clients/stellarcore/client.go | 10 ---------- clients/stellarcore/metrics_client.go | 4 ++-- services/horizon/internal/httpx/router.go | 2 +- services/horizon/internal/txsub/submitter.go | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/clients/stellarcore/client.go b/clients/stellarcore/client.go index 3e5a9fb9e9..931a076021 100644 --- a/clients/stellarcore/client.go +++ b/clients/stellarcore/client.go @@ -29,16 +29,6 @@ type Client struct { URL string } -type ClientInterface interface { - Upgrade(ctx context.Context, version int) (err error) - GetLedgerEntry(ctx context.Context, ledgerKey xdr.LedgerKey) (proto.GetLedgerEntryResponse, error) - Info(ctx context.Context) (resp *proto.InfoResponse, err error) - SetCursor(ctx context.Context, id string, cursor int32) (err error) - SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) - WaitForNetworkSync(ctx context.Context) error - ManualClose(ctx context.Context) (err error) -} - // drainReponse is a helper method for draining the body stream off the http // response object and optionally close the stream. It would also update the // error but only as long as there wasn't an error before - this would allow diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 02ac9e5290..bce1897f08 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -13,7 +13,7 @@ type ClientWithMetrics interface { } type clientWithMetrics struct { - CoreClient ClientInterface + CoreClient Client TxSubMetrics struct { // SubmissionDuration exposes timing metrics about the rate and latency of @@ -69,7 +69,7 @@ func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.Tr } } -func NewClientWithMetrics(client ClientInterface, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { +func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { submissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: "horizon", Subsystem: prometheusSubsystem, diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index b5fd9af654..63c70920ab 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -338,7 +338,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate NetworkPassphrase: config.NetworkPassphrase, DisableTxSub: config.DisableTxSub, CoreStateGetter: config.CoreGetter, - ClientWithMetrics: stellarcore.NewClientWithMetrics(&stellarcore.Client{ + ClientWithMetrics: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: http.DefaultClient, URL: config.StellarCoreURL, }, config.PrometheusRegistry, "async_txsub"), diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index 4f2ed13978..1ac78754a2 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -18,7 +18,7 @@ import ( // `h`. func NewDefaultSubmitter(h *http.Client, url string, registry *prometheus.Registry) Submitter { return &submitter{ - StellarCore: stellarcore.NewClientWithMetrics(&stellarcore.Client{ + StellarCore: stellarcore.NewClientWithMetrics(stellarcore.Client{ HTTP: h, URL: url, }, registry, "txsub"), From f5ddbf288e94b1f20279ab88fff025cba9a53fbc Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 26 Mar 2024 11:08:50 -0400 Subject: [PATCH 31/68] Remove HTTP Status from submission response --- protocols/horizon/main.go | 2 -- .../friendbot/internal/friendbot_handler.go | 4 ++-- .../actions/submit_transaction_async.go | 15 +++------------ .../actions/submit_transaction_async_test.go | 18 +++++++----------- .../internal/integration/txsub_async_test.go | 4 ---- support/render/hal/handler.go | 16 ---------------- support/render/hal/io.go | 12 ------------ support/render/httpjson/io.go | 16 +++++++++++++++- 8 files changed, 27 insertions(+), 60 deletions(-) delete mode 100644 support/render/hal/handler.go delete mode 100644 support/render/hal/io.go diff --git a/protocols/horizon/main.go b/protocols/horizon/main.go index 959ab895b9..c6ab64baae 100644 --- a/protocols/horizon/main.go +++ b/protocols/horizon/main.go @@ -581,8 +581,6 @@ type AsyncTransactionSubmissionResponse struct { // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, // proto.TXStatusTryAgainLater, or proto.TXStatusError. TxStatus string `json:"tx_status"` - // HttpStatus represents the corresponding http status code. - HttpStatus int `json:"status"` // Hash is a hash of the transaction which can be used to look up whether // the transaction was included in the ledger. Hash string `json:"hash"` diff --git a/services/friendbot/internal/friendbot_handler.go b/services/friendbot/internal/friendbot_handler.go index 9e6f3f8d74..e56da2c72e 100644 --- a/services/friendbot/internal/friendbot_handler.go +++ b/services/friendbot/internal/friendbot_handler.go @@ -1,12 +1,12 @@ package internal import ( + "github.com/stellar/go/support/render/httpjson" "net/http" "net/url" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/strkey" - "github.com/stellar/go/support/render/hal" "github.com/stellar/go/support/render/problem" ) @@ -23,7 +23,7 @@ func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) return } - hal.Render(w, *result) + httpjson.Render(w, *result, httpjson.HALJSON) } // doHandle is just a convenience method that returns the object to be rendered diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 21ac93f933..9688c2cd13 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -9,13 +9,6 @@ import ( "net/http" ) -var coreStatusToHTTPStatus = map[string]int{ - proto.TXStatusPending: http.StatusCreated, - proto.TXStatusDuplicate: http.StatusConflict, - proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, - proto.TXStatusError: http.StatusBadRequest, -} - type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool @@ -107,20 +100,18 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, TxStatus: resp.Status, - HttpStatus: coreStatusToHTTPStatus[resp.Status], Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: return horizon.AsyncTransactionSubmissionResponse{ - TxStatus: resp.Status, - HttpStatus: coreStatusToHTTPStatus[resp.Status], - Hash: info.hash, + TxStatus: resp.Status, + Hash: info.hash, }, nil default: return nil, &problem.P{ Type: "transaction_submission_invalid_status", Title: "Transaction Submission Invalid Status", - Status: http.StatusBadRequest, + Status: http.StatusInternalServerError, Detail: "Received invalid status from stellar-core." + "The `extras.error` field on this response contains further " + "details. Descriptions of each code can be found at: " + diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 600e2d25a9..2d8fb06497 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -29,7 +29,7 @@ func createRequest() *http.Request { request, _ := http.NewRequest( "POST", - "http://localhost:8000/v2/transactions", + "http://localhost:8000/v2/transactions_async", strings.NewReader(form.Encode()), ) request.Header.Add("Content-Type", "application/x-www-form-urlencoded") @@ -163,7 +163,6 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { ErrorResultXDR: "test-error", DiagnosticEventsXDR: "test-diagnostic-events", TxStatus: proto.TXStatusError, - HttpStatus: http.StatusBadRequest, Hash: TxHash, }, }, @@ -172,9 +171,8 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { Status: proto.TXStatusPending, }, expectedResponse: horizon.AsyncTransactionSubmissionResponse{ - TxStatus: proto.TXStatusPending, - HttpStatus: http.StatusCreated, - Hash: TxHash, + TxStatus: proto.TXStatusPending, + Hash: TxHash, }, }, { @@ -182,9 +180,8 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { Status: proto.TXStatusDuplicate, }, expectedResponse: horizon.AsyncTransactionSubmissionResponse{ - TxStatus: proto.TXStatusDuplicate, - HttpStatus: http.StatusConflict, - Hash: TxHash, + TxStatus: proto.TXStatusDuplicate, + Hash: TxHash, }, }, { @@ -192,9 +189,8 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { Status: proto.TXStatusTryAgainLater, }, expectedResponse: horizon.AsyncTransactionSubmissionResponse{ - TxStatus: proto.TXStatusTryAgainLater, - HttpStatus: http.StatusServiceUnavailable, - Hash: TxHash, + TxStatus: proto.TXStatusTryAgainLater, + Hash: TxHash, }, }, } diff --git a/services/horizon/internal/integration/txsub_async_test.go b/services/horizon/internal/integration/txsub_async_test.go index e0e55c06ff..9bebc467b4 100644 --- a/services/horizon/internal/integration/txsub_async_test.go +++ b/services/horizon/internal/integration/txsub_async_test.go @@ -38,7 +38,6 @@ func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { ErrorResultXDR: "", DiagnosticEventsXDR: "", TxStatus: "PENDING", - HttpStatus: 201, Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) } @@ -71,7 +70,6 @@ func TestAsyncTxSub_SubmissionError(t *testing.T) { ErrorResultXDR: "AAAAAAAAAGT////7AAAAAA==", DiagnosticEventsXDR: "", TxStatus: "ERROR", - HttpStatus: 400, Hash: "0684df00f20efd5876f1b8d17bc6d3a68d8b85c06bb41e448815ecaa6307a251", }) } @@ -104,7 +102,6 @@ func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) { ErrorResultXDR: "", DiagnosticEventsXDR: "", TxStatus: "PENDING", - HttpStatus: 201, Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) @@ -114,7 +111,6 @@ func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) { ErrorResultXDR: "", DiagnosticEventsXDR: "", TxStatus: "TRY_AGAIN_LATER", - HttpStatus: 503, Hash: "d5eb72a4c1832b89965850fff0bd9bba4b6ca102e7c89099dcaba5e7d7d2e049", }) } diff --git a/support/render/hal/handler.go b/support/render/hal/handler.go deleted file mode 100644 index 2eaa4b1e2c..0000000000 --- a/support/render/hal/handler.go +++ /dev/null @@ -1,16 +0,0 @@ -package hal - -import ( - "context" - "net/http" - - "github.com/stellar/go/support/render/httpjson" -) - -func Handler(fn, param interface{}) (http.Handler, error) { - return httpjson.Handler(fn, param, httpjson.HALJSON) -} - -func ExecuteFunc(ctx context.Context, fn, param interface{}) (interface{}, bool, error) { - return httpjson.ExecuteFunc(ctx, fn, param, httpjson.HALJSON) -} diff --git a/support/render/hal/io.go b/support/render/hal/io.go deleted file mode 100644 index 2b1bbc71b7..0000000000 --- a/support/render/hal/io.go +++ /dev/null @@ -1,12 +0,0 @@ -package hal - -import ( - "net/http" - - "github.com/stellar/go/support/render/httpjson" -) - -// Render write data to w, after marshaling to json -func Render(w http.ResponseWriter, data interface{}) { - httpjson.Render(w, data, httpjson.HALJSON) -} diff --git a/support/render/httpjson/io.go b/support/render/httpjson/io.go index 2071629462..9b08cb4ea0 100644 --- a/support/render/httpjson/io.go +++ b/support/render/httpjson/io.go @@ -2,6 +2,8 @@ package httpjson import ( "encoding/json" + "github.com/stellar/go/protocols/horizon" + proto "github.com/stellar/go/protocols/stellarcore" "net/http" "github.com/stellar/go/support/errors" @@ -9,6 +11,13 @@ import ( type contentType int +var coreStatusToHTTPStatus = map[string]int{ + proto.TXStatusPending: http.StatusCreated, + proto.TXStatusDuplicate: http.StatusConflict, + proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, + proto.TXStatusError: http.StatusBadRequest, +} + const ( JSON contentType = iota HALJSON @@ -27,7 +36,12 @@ func renderToString(data interface{}, pretty bool) ([]byte, error) { // Render write data to w, after marshaling to json. The response header is // set based on cType. func Render(w http.ResponseWriter, data interface{}, cType contentType) { - RenderStatus(w, http.StatusOK, data, cType) + statusCode := http.StatusOK + if asyncTxSubResponse, ok := data.(horizon.AsyncTransactionSubmissionResponse); ok { + statusCode = coreStatusToHTTPStatus[asyncTxSubResponse.TxStatus] + } + + RenderStatus(w, statusCode, data, cType) } // RenderStatus write data to w, after marshaling to json. From 5e498fb044b4f6d3e36fb2ef660dd868366f70fa Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 26 Mar 2024 14:58:02 -0400 Subject: [PATCH 32/68] Add logging statements --- .../actions/submit_transaction_async.go | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 9688c2cd13..da91609a03 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -5,10 +5,16 @@ import ( "github.com/stellar/go/protocols/horizon" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/problem" "net/http" ) +var ( + logger = log.New().WithField("service", "async-txsub") +) + type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool @@ -17,16 +23,21 @@ type AsyncSubmitTransactionHandler struct { } func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { + logger.SetLevel(log.DebugLevel) + if err := validateBodyType(r); err != nil { + logger.WithError(err).Error("Could not validate request body type") return nil, err } raw, err := getString(r, "tx") if err != nil { + logger.WithError(err).Error("Could not read transaction string from request URL") return nil, err } if handler.DisableTxSub { + logger.WithField("envelope_xdr", raw).Error("Could not submit transaction: transaction submission is disabled") return nil, &problem.P{ Type: "transaction_submission_disabled", Title: "Transaction Submission Disabled", @@ -41,6 +52,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http info, err := extractEnvelopeInfo(raw, handler.NetworkPassphrase) if err != nil { + logger.WithField("envelope_xdr", raw).Error("Could not parse transaction envelope") return nil, &problem.P{ Type: "transaction_malformed", Title: "Transaction Malformed", @@ -58,11 +70,13 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http coreState := handler.GetCoreState() if !coreState.Synced { + logger.WithField("envelope_xdr", raw).Error("Stellar-core is not synced") return nil, hProblem.StaleHistory } resp, err := handler.ClientWithMetrics.SubmitTransaction(r.Context(), info.raw, info.parsed) if err != nil { + logger.WithField("envelope_xdr", raw).WithError(err).Error("Transaction submission to stellar-core failed") return nil, &problem.P{ Type: "transaction_submission_failed", Title: "Transaction Submission Failed", @@ -79,6 +93,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } if resp.IsException() { + logger.WithField("envelope_xdr", raw).WithError(errors.Errorf(resp.Exception)).Error("Transaction submission exception from stellar-core") return nil, &problem.P{ Type: "transaction_submission_exception", Title: "Transaction Submission Exception", @@ -96,6 +111,12 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http switch resp.Status { case proto.TXStatusError: + logger.WithFields(log.F{ + "envelope_xdr": raw, + "error_result_xdr": resp.Error, + "status": resp.Status, + "hash": info.hash, + }).Error("Transaction submitted to stellar-core") return horizon.AsyncTransactionSubmissionResponse{ ErrorResultXDR: resp.Error, DiagnosticEventsXDR: resp.DiagnosticEvents, @@ -103,11 +124,17 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http Hash: info.hash, }, nil case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: + logger.WithFields(log.F{ + "envelope_xdr": raw, + "status": resp.Status, + "hash": info.hash, + }).Error("Transaction submitted to stellar-core") return horizon.AsyncTransactionSubmissionResponse{ TxStatus: resp.Status, Hash: info.hash, }, nil default: + logger.WithField("envelope_xdr", raw).WithError(errors.Errorf(resp.Error)).Error("Received invalid submission status from stellar-core") return nil, &problem.P{ Type: "transaction_submission_invalid_status", Title: "Transaction Submission Invalid Status", From 48932aac01b92f8e1f39b4e7e84d2da2f5532111 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 27 Mar 2024 10:49:31 -0400 Subject: [PATCH 33/68] Fix failing integration tests --- clients/horizonclient/internal.go | 5 ++- protocols/horizon/main.go | 3 -- .../actions/submit_transaction_async.go | 36 ++++++++----------- .../actions/submit_transaction_async_test.go | 7 ++-- .../httpx/static/txsub_async_oapi.yaml | 13 ------- .../internal/integration/txsub_async_test.go | 27 ++++++-------- 6 files changed, 33 insertions(+), 58 deletions(-) diff --git a/clients/horizonclient/internal.go b/clients/horizonclient/internal.go index 123788caa9..b11131e343 100644 --- a/clients/horizonclient/internal.go +++ b/clients/horizonclient/internal.go @@ -27,7 +27,10 @@ func decodeResponse(resp *http.Response, object interface{}, horizonUrl string, } setCurrentServerTime(u.Hostname(), resp.Header["Date"], clock) - if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { + // While this part of code assumes that any error < 200 or error >= 300 is a Horizon problem, it is not + // true for the response from /transactions_async endpoint which does give these codes for certain responses + // from core. + if !(resp.StatusCode >= 200 && resp.StatusCode < 300) && resp.Request.URL.Path != "/transactions_async" { horizonError := &Error{ Response: resp, } diff --git a/protocols/horizon/main.go b/protocols/horizon/main.go index c6ab64baae..011fe13565 100644 --- a/protocols/horizon/main.go +++ b/protocols/horizon/main.go @@ -574,9 +574,6 @@ type AsyncTransactionSubmissionResponse struct { // ErrorResultXDR is a TransactionResult xdr string which contains details on why // the transaction could not be accepted by stellar-core. ErrorResultXDR string `json:"errorResultXdr,omitempty"` - // DiagnosticEventsXDR is present only if Status is equal to proto.TXStatusError. - // DiagnosticEventsXDR is a base64-encoded slice of xdr.DiagnosticEvent - DiagnosticEventsXDR string `json:"diagnosticEventsXdr,omitempty"` // TxStatus represents the status of the transaction submission returned by stellar-core. // It can be one of: proto.TXStatusPending, proto.TXStatusDuplicate, // proto.TXStatusTryAgainLater, or proto.TXStatusError. diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index da91609a03..0fdc372b2a 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -110,29 +110,23 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } switch resp.Status { - case proto.TXStatusError: - logger.WithFields(log.F{ - "envelope_xdr": raw, - "error_result_xdr": resp.Error, - "status": resp.Status, - "hash": info.hash, - }).Error("Transaction submitted to stellar-core") - return horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: resp.Error, - DiagnosticEventsXDR: resp.DiagnosticEvents, - TxStatus: resp.Status, - Hash: info.hash, - }, nil - case proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: - logger.WithFields(log.F{ - "envelope_xdr": raw, - "status": resp.Status, - "hash": info.hash, - }).Error("Transaction submitted to stellar-core") - return horizon.AsyncTransactionSubmissionResponse{ + case proto.TXStatusError, proto.TXStatusPending, proto.TXStatusDuplicate, proto.TXStatusTryAgainLater: + response := horizon.AsyncTransactionSubmissionResponse{ TxStatus: resp.Status, Hash: info.hash, - }, nil + } + + if resp.Status == proto.TXStatusError { + logger.WithFields(log.F{ + "envelope_xdr": raw, + "error_xdr": resp.Error, + "status": resp.Status, + "hash": info.hash, + }).Error("Transaction submission to stellar-core resulted in ERROR status") + response.ErrorResultXDR = resp.Error + } + + return response, nil default: logger.WithField("envelope_xdr", raw).WithError(errors.Errorf(resp.Error)).Error("Received invalid submission status from stellar-core") return nil, &problem.P{ diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index 2d8fb06497..bf987eba1a 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -160,10 +160,9 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { DiagnosticEvents: "test-diagnostic-events", }, expectedResponse: horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: "test-error", - DiagnosticEventsXDR: "test-diagnostic-events", - TxStatus: proto.TXStatusError, - Hash: TxHash, + ErrorResultXDR: "test-error", + TxStatus: proto.TXStatusError, + Hash: TxHash, }, }, { diff --git a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml index b90b30f302..c4c86d29a7 100644 --- a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml +++ b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml @@ -29,7 +29,6 @@ paths: $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' example: tx_status: "PENDING" - status: 201 hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" '400': @@ -54,9 +53,7 @@ paths: summary: ERROR Status from core value: errorResultXdr: "AAAAAAAAAGT////7AAAAAA==" - diagnosticEventsXdr: "AAAAAAAAAGT////7AAAAAA==" tx_status: "ERROR" - status: 400 hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" '405': description: Transaction submission has been disabled for Horizon. @@ -82,9 +79,7 @@ paths: $ref: '#/components/schemas/AsyncTransactionSubmissionResponse' example: errorResultXdr: "" - diagnosticEventsXdr: "" tx_status: "DUPLICATE" - status: 409 hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" '500': description: Transaction is a duplicate of a previously submitted transaction. @@ -133,7 +128,6 @@ paths: summary: TRY_AGAIN_LATER Status from core value: tx_status: "TRY_AGAIN_LATER" - status: 503 hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165" @@ -146,17 +140,10 @@ components: type: string nullable: true description: TransactionResult XDR string which is present only if the submission status from core is an ERROR. - diagnosticEventsXdr: - type: string - nullable: true - description: Base64-encoded slice of xdr.DiagnosticEvent. It is present only if the submission status from core is an ERROR. tx_status: type: string enum: ["ERROR", "PENDING", "DUPLICATE", "TRY_AGAIN_LATER"] description: Status of the transaction submission. - status: - type: integer - description: Corresponding HTTP status code. hash: type: string description: Hash of the transaction. diff --git a/services/horizon/internal/integration/txsub_async_test.go b/services/horizon/internal/integration/txsub_async_test.go index 9bebc467b4..38b03ea88c 100644 --- a/services/horizon/internal/integration/txsub_async_test.go +++ b/services/horizon/internal/integration/txsub_async_test.go @@ -35,10 +35,8 @@ func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { txResp, err := itest.AsyncSubmitTransaction(master, txParams) assert.NoError(t, err) assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: "", - DiagnosticEventsXDR: "", - TxStatus: "PENDING", - Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", + TxStatus: "PENDING", + Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) } @@ -67,10 +65,9 @@ func TestAsyncTxSub_SubmissionError(t *testing.T) { txResp, err := itest.AsyncSubmitTransaction(master, txParams) assert.NoError(t, err) assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: "AAAAAAAAAGT////7AAAAAA==", - DiagnosticEventsXDR: "", - TxStatus: "ERROR", - Hash: "0684df00f20efd5876f1b8d17bc6d3a68d8b85c06bb41e448815ecaa6307a251", + ErrorResultXDR: "AAAAAAAAAGT////7AAAAAA==", + TxStatus: "ERROR", + Hash: "0684df00f20efd5876f1b8d17bc6d3a68d8b85c06bb41e448815ecaa6307a251", }) } @@ -99,19 +96,17 @@ func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) { txResp, err := itest.AsyncSubmitTransaction(master, txParams) assert.NoError(t, err) assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: "", - DiagnosticEventsXDR: "", - TxStatus: "PENDING", - Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", + ErrorResultXDR: "", + TxStatus: "PENDING", + Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) txResp, err = itest.AsyncSubmitTransaction(master, txParams) assert.NoError(t, err) assert.Equal(t, txResp, horizon.AsyncTransactionSubmissionResponse{ - ErrorResultXDR: "", - DiagnosticEventsXDR: "", - TxStatus: "TRY_AGAIN_LATER", - Hash: "d5eb72a4c1832b89965850fff0bd9bba4b6ca102e7c89099dcaba5e7d7d2e049", + ErrorResultXDR: "", + TxStatus: "TRY_AGAIN_LATER", + Hash: "d5eb72a4c1832b89965850fff0bd9bba4b6ca102e7c89099dcaba5e7d7d2e049", }) } From 9f50dddb3997ab9cbac812f0612d086652f21691 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 27 Mar 2024 11:11:41 -0400 Subject: [PATCH 34/68] Fix failing tests - 1 --- clients/horizonclient/internal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/horizonclient/internal.go b/clients/horizonclient/internal.go index b11131e343..9dc6052263 100644 --- a/clients/horizonclient/internal.go +++ b/clients/horizonclient/internal.go @@ -30,7 +30,7 @@ func decodeResponse(resp *http.Response, object interface{}, horizonUrl string, // While this part of code assumes that any error < 200 or error >= 300 is a Horizon problem, it is not // true for the response from /transactions_async endpoint which does give these codes for certain responses // from core. - if !(resp.StatusCode >= 200 && resp.StatusCode < 300) && resp.Request.URL.Path != "/transactions_async" { + if !(resp.StatusCode >= 200 && resp.StatusCode < 300) && (resp.Request == nil || resp.Request.URL == nil || resp.Request.URL.Path != "/transactions_async") { horizonError := &Error{ Response: resp, } From f34abbcaaad34317bfad117cb67762194e6b32bf Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 12:14:19 -0400 Subject: [PATCH 35/68] Add back deleted files --- support/render/hal/handler.go | 16 ++++++++++++++++ support/render/hal/io.go | 12 ++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 support/render/hal/handler.go create mode 100644 support/render/hal/io.go diff --git a/support/render/hal/handler.go b/support/render/hal/handler.go new file mode 100644 index 0000000000..2eaa4b1e2c --- /dev/null +++ b/support/render/hal/handler.go @@ -0,0 +1,16 @@ +package hal + +import ( + "context" + "net/http" + + "github.com/stellar/go/support/render/httpjson" +) + +func Handler(fn, param interface{}) (http.Handler, error) { + return httpjson.Handler(fn, param, httpjson.HALJSON) +} + +func ExecuteFunc(ctx context.Context, fn, param interface{}) (interface{}, bool, error) { + return httpjson.ExecuteFunc(ctx, fn, param, httpjson.HALJSON) +} diff --git a/support/render/hal/io.go b/support/render/hal/io.go new file mode 100644 index 0000000000..2b1bbc71b7 --- /dev/null +++ b/support/render/hal/io.go @@ -0,0 +1,12 @@ +package hal + +import ( + "net/http" + + "github.com/stellar/go/support/render/httpjson" +) + +// Render write data to w, after marshaling to json +func Render(w http.ResponseWriter, data interface{}) { + httpjson.Render(w, data, httpjson.HALJSON) +} From 8d527f2cd748b6fe610f22abab24201730537e01 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 12:25:16 -0400 Subject: [PATCH 36/68] Remove circular import --- support/render/hal/handler.go | 16 ---------------- support/render/hal/io.go | 12 ------------ 2 files changed, 28 deletions(-) delete mode 100644 support/render/hal/handler.go delete mode 100644 support/render/hal/io.go diff --git a/support/render/hal/handler.go b/support/render/hal/handler.go deleted file mode 100644 index 2eaa4b1e2c..0000000000 --- a/support/render/hal/handler.go +++ /dev/null @@ -1,16 +0,0 @@ -package hal - -import ( - "context" - "net/http" - - "github.com/stellar/go/support/render/httpjson" -) - -func Handler(fn, param interface{}) (http.Handler, error) { - return httpjson.Handler(fn, param, httpjson.HALJSON) -} - -func ExecuteFunc(ctx context.Context, fn, param interface{}) (interface{}, bool, error) { - return httpjson.ExecuteFunc(ctx, fn, param, httpjson.HALJSON) -} diff --git a/support/render/hal/io.go b/support/render/hal/io.go deleted file mode 100644 index 2b1bbc71b7..0000000000 --- a/support/render/hal/io.go +++ /dev/null @@ -1,12 +0,0 @@ -package hal - -import ( - "net/http" - - "github.com/stellar/go/support/render/httpjson" -) - -// Render write data to w, after marshaling to json -func Render(w http.ResponseWriter, data interface{}) { - httpjson.Render(w, data, httpjson.HALJSON) -} From 9b3deb9f28ddaca77541fbb0f316d46d4aaa8e31 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 14:28:28 -0400 Subject: [PATCH 37/68] Group metrics into submission duration --- clients/stellarcore/metrics_client.go | 58 +++++---------------------- 1 file changed, 10 insertions(+), 48 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index bce1897f08..c27e3e5aea 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -20,10 +20,6 @@ type clientWithMetrics struct { // submissions to stellar-core SubmissionDuration *prometheus.SummaryVec - // SubmissionsCounter tracks the rate of transactions that have - // been submitted to this process - SubmissionsCounter *prometheus.CounterVec - // V0TransactionsCounter tracks the rate of v0 transaction envelopes that // have been submitted to this process V0TransactionsCounter *prometheus.CounterVec @@ -49,24 +45,23 @@ func (c *clientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { var label prometheus.Labels if err != nil { - label = prometheus.Labels{"status": "request_error"} + label["status"] = "request_error" } else if response.IsException() { - label = prometheus.Labels{"status": "exception"} + label["status"] = "exception" } else { - label = prometheus.Labels{"status": response.Status} + label["status"] = response.Status } - c.TxSubMetrics.SubmissionDuration.With(label).Observe(duration) - c.TxSubMetrics.SubmissionsCounter.With(label).Inc() - switch envelope.Type { case xdr.EnvelopeTypeEnvelopeTypeTxV0: - c.TxSubMetrics.V0TransactionsCounter.With(label).Inc() + label["envelope_type"] = "v0" case xdr.EnvelopeTypeEnvelopeTypeTx: - c.TxSubMetrics.V1TransactionsCounter.With(label).Inc() + label["envelope_type"] = "v1" case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: - c.TxSubMetrics.FeeBumpTransactionsCounter.With(label).Inc() + label["envelope_type"] = "fee-bump" } + + c.TxSubMetrics.SubmissionDuration.With(label).Observe(duration) } func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { @@ -76,54 +71,21 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe Name: "submission_duration_seconds", Help: "submission durations to Stellar-Core, sliding window = 10m", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"status"}) - submissionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "horizon", - Subsystem: prometheusSubsystem, - Name: "submissions_count", - Help: "number of submissions, sliding window = 10m", - }, []string{"status"}) - v0TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "horizon", - Subsystem: prometheusSubsystem, - Name: "v0_count", - Help: "number of v0 transaction envelopes submitted, sliding window = 10m", - }, []string{"status"}) - v1TransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "horizon", - Subsystem: prometheusSubsystem, - Name: "v1_count", - Help: "number of v1 transaction envelopes submitted, sliding window = 10m", - }, []string{"status"}) - feeBumpTransactionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: "horizon", - Subsystem: prometheusSubsystem, - Name: "feebump_count", - Help: "number of fee bump transaction envelopes submitted, sliding window = 10m", - }, []string{"status"}) + }, []string{"status", "envelope_type"}) registry.MustRegister( submissionDuration, - submissionsCounter, - v0TransactionsCounter, - v1TransactionsCounter, - feeBumpTransactionsCounter, ) return &clientWithMetrics{ CoreClient: client, TxSubMetrics: struct { SubmissionDuration *prometheus.SummaryVec - SubmissionsCounter *prometheus.CounterVec V0TransactionsCounter *prometheus.CounterVec V1TransactionsCounter *prometheus.CounterVec FeeBumpTransactionsCounter *prometheus.CounterVec }{ - SubmissionDuration: submissionDuration, - SubmissionsCounter: submissionsCounter, - V0TransactionsCounter: v0TransactionsCounter, - V1TransactionsCounter: v1TransactionsCounter, - FeeBumpTransactionsCounter: feeBumpTransactionsCounter, + SubmissionDuration: submissionDuration, }, } } From ee31a95fabb0d69da6aa0fb2fe23a4ebffb7ff5a Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 14:49:59 -0400 Subject: [PATCH 38/68] Group metrics into submission duration - 2 --- clients/stellarcore/metrics_client.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index c27e3e5aea..7e1759ee43 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -2,10 +2,12 @@ package stellarcore import ( "context" + "time" + "github.com/prometheus/client_golang/prometheus" + proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/xdr" - "time" ) type ClientWithMetrics interface { @@ -43,7 +45,7 @@ func (c *clientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, } func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { - var label prometheus.Labels + label := prometheus.Labels{} if err != nil { label["status"] = "request_error" } else if response.IsException() { From c646e0b208fed01414a06951383bb0de6067dbc4 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 15:00:30 -0400 Subject: [PATCH 39/68] Remove logging statements where not needed --- .../internal/actions/submit_transaction_async.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 0fdc372b2a..d7cf2dbd92 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -1,6 +1,8 @@ package actions import ( + "net/http" + "github.com/stellar/go/clients/stellarcore" "github.com/stellar/go/protocols/horizon" proto "github.com/stellar/go/protocols/stellarcore" @@ -8,7 +10,6 @@ import ( "github.com/stellar/go/support/errors" "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/problem" - "net/http" ) var ( @@ -26,18 +27,15 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http logger.SetLevel(log.DebugLevel) if err := validateBodyType(r); err != nil { - logger.WithError(err).Error("Could not validate request body type") return nil, err } raw, err := getString(r, "tx") if err != nil { - logger.WithError(err).Error("Could not read transaction string from request URL") return nil, err } if handler.DisableTxSub { - logger.WithField("envelope_xdr", raw).Error("Could not submit transaction: transaction submission is disabled") return nil, &problem.P{ Type: "transaction_submission_disabled", Title: "Transaction Submission Disabled", @@ -52,7 +50,6 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http info, err := extractEnvelopeInfo(raw, handler.NetworkPassphrase) if err != nil { - logger.WithField("envelope_xdr", raw).Error("Could not parse transaction envelope") return nil, &problem.P{ Type: "transaction_malformed", Title: "Transaction Malformed", @@ -70,13 +67,11 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http coreState := handler.GetCoreState() if !coreState.Synced { - logger.WithField("envelope_xdr", raw).Error("Stellar-core is not synced") return nil, hProblem.StaleHistory } resp, err := handler.ClientWithMetrics.SubmitTransaction(r.Context(), info.raw, info.parsed) if err != nil { - logger.WithField("envelope_xdr", raw).WithError(err).Error("Transaction submission to stellar-core failed") return nil, &problem.P{ Type: "transaction_submission_failed", Title: "Transaction Submission Failed", @@ -93,7 +88,6 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } if resp.IsException() { - logger.WithField("envelope_xdr", raw).WithError(errors.Errorf(resp.Exception)).Error("Transaction submission exception from stellar-core") return nil, &problem.P{ Type: "transaction_submission_exception", Title: "Transaction Submission Exception", From 42cd689af33ecbb391aaae15cc689dbc2fb0eaa2 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 15:01:27 -0400 Subject: [PATCH 40/68] Change to internal server error --- services/horizon/internal/httpx/router.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 213869a509..7729a7b2a5 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -3,12 +3,13 @@ package httpx import ( "compress/flate" "fmt" - "github.com/stellar/go/clients/stellarcore" "net/http" "net/http/pprof" "net/url" "time" + "github.com/stellar/go/clients/stellarcore" + "github.com/go-chi/chi" chimiddleware "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus" @@ -388,7 +389,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate r.Internal.Get("/transactions_async", func(w http.ResponseWriter, r *http.Request) { p, err := staticFiles.ReadFile("static/txsub_async_oapi.yaml") if err != nil { - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/openapi+yaml") From 21687d2da41cfafb971e67af4a8a588b2a625bb4 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 15:12:33 -0400 Subject: [PATCH 41/68] Use request context logger --- .../horizon/internal/actions/submit_transaction_async.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index d7cf2dbd92..f16446dd0b 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -12,10 +12,6 @@ import ( "github.com/stellar/go/support/render/problem" ) -var ( - logger = log.New().WithField("service", "async-txsub") -) - type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool @@ -24,7 +20,7 @@ type AsyncSubmitTransactionHandler struct { } func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { - logger.SetLevel(log.DebugLevel) + logger := log.Ctx(r.Context()) if err := validateBodyType(r); err != nil { return nil, err From e9318930ab8244bf180745fc72eadf9555736a4f Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Tue, 2 Apr 2024 17:18:26 -0400 Subject: [PATCH 42/68] Use interface method for setting http status --- .../internal/actions/claimable_balance.go | 4 ++++ services/horizon/internal/actions/fee_stats.go | 4 ++++ services/horizon/internal/actions/ledger.go | 4 ++++ .../horizon/internal/actions/liquidity_pool.go | 4 ++++ services/horizon/internal/actions/offer.go | 4 ++++ services/horizon/internal/actions/operation.go | 4 ++++ services/horizon/internal/actions/path.go | 8 ++++++++ services/horizon/internal/actions/root.go | 4 ++++ .../internal/actions/submit_transaction.go | 9 +++++++-- .../internal/actions/submit_transaction_async.go | 15 +++++++++++++++ services/horizon/internal/actions/trade.go | 4 ++++ services/horizon/internal/actions/transaction.go | 4 ++++ services/horizon/internal/httpx/handler.go | 5 ++++- support/render/httpjson/io.go | 16 +--------------- 14 files changed, 71 insertions(+), 18 deletions(-) diff --git a/services/horizon/internal/actions/claimable_balance.go b/services/horizon/internal/actions/claimable_balance.go index dbfa02c298..a1a2876a3f 100644 --- a/services/horizon/internal/actions/claimable_balance.go +++ b/services/horizon/internal/actions/claimable_balance.go @@ -20,6 +20,10 @@ import ( // GetClaimableBalanceByIDHandler is the action handler for all end-points returning a claimable balance. type GetClaimableBalanceByIDHandler struct{} +func (handler GetClaimableBalanceByIDHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // ClaimableBalanceQuery query struct for claimables_balances/id end-point type ClaimableBalanceQuery struct { ID string `schema:"id" valid:"claimableBalanceID,required"` diff --git a/services/horizon/internal/actions/fee_stats.go b/services/horizon/internal/actions/fee_stats.go index 5a51d01c4b..56b9207288 100644 --- a/services/horizon/internal/actions/fee_stats.go +++ b/services/horizon/internal/actions/fee_stats.go @@ -12,6 +12,10 @@ import ( type FeeStatsHandler struct { } +func (handler FeeStatsHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // GetResource fee stats resource func (handler FeeStatsHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { feeStats := horizon.FeeStats{} diff --git a/services/horizon/internal/actions/ledger.go b/services/horizon/internal/actions/ledger.go index 37fddb5bd9..5fadf15ad6 100644 --- a/services/horizon/internal/actions/ledger.go +++ b/services/horizon/internal/actions/ledger.go @@ -59,6 +59,10 @@ type GetLedgerByIDHandler struct { LedgerState *ledger.State } +func (handler GetLedgerByIDHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + func (handler GetLedgerByIDHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { qp := LedgerByIDQuery{} err := getParams(&qp, r) diff --git a/services/horizon/internal/actions/liquidity_pool.go b/services/horizon/internal/actions/liquidity_pool.go index bb1aedd882..eaacf485e6 100644 --- a/services/horizon/internal/actions/liquidity_pool.go +++ b/services/horizon/internal/actions/liquidity_pool.go @@ -19,6 +19,10 @@ import ( // GetLiquidityPoolByIDHandler is the action handler for all end-points returning a liquidity pool. type GetLiquidityPoolByIDHandler struct{} +func (handler GetLiquidityPoolByIDHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // LiquidityPoolQuery query struct for liquidity_pools/id endpoint type LiquidityPoolQuery struct { ID string `schema:"liquidity_pool_id" valid:"sha256"` diff --git a/services/horizon/internal/actions/offer.go b/services/horizon/internal/actions/offer.go index c5de593c4b..24d5bc6b71 100644 --- a/services/horizon/internal/actions/offer.go +++ b/services/horizon/internal/actions/offer.go @@ -21,6 +21,10 @@ type OfferByIDQuery struct { // GetOfferByID is the action handler for the /offers/{id} endpoint type GetOfferByID struct{} +func (handler GetOfferByID) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // GetResource returns an offer by id. func (handler GetOfferByID) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/actions/operation.go b/services/horizon/internal/actions/operation.go index f59191aee0..09ab346a11 100644 --- a/services/horizon/internal/actions/operation.go +++ b/services/horizon/internal/actions/operation.go @@ -136,6 +136,10 @@ type GetOperationByIDHandler struct { SkipTxMeta bool } +func (handler GetOperationByIDHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // OperationQuery query struct for operation/id end-point type OperationQuery struct { LedgerState *ledger.State `valid:"-"` diff --git a/services/horizon/internal/actions/path.go b/services/horizon/internal/actions/path.go index fca06fa970..2d25053eca 100644 --- a/services/horizon/internal/actions/path.go +++ b/services/horizon/internal/actions/path.go @@ -29,6 +29,10 @@ type FindPathsHandler struct { PathFinder paths.Finder } +func (handler FindPathsHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // StrictReceivePathsQuery query struct for paths/strict-send end-point type StrictReceivePathsQuery struct { SourceAssets string `schema:"source_assets" valid:"-"` @@ -208,6 +212,10 @@ type FindFixedPathsHandler struct { PathFinder paths.Finder } +func (handler FindFixedPathsHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // DestinationAssetsOrDestinationAccountProblem custom error where destination asserts or accounts are required var DestinationAssetsOrDestinationAccountProblem = problem.P{ Type: "bad_request", diff --git a/services/horizon/internal/actions/root.go b/services/horizon/internal/actions/root.go index 42bbc1b3b3..11b1782953 100644 --- a/services/horizon/internal/actions/root.go +++ b/services/horizon/internal/actions/root.go @@ -17,6 +17,10 @@ type GetRootHandler struct { HorizonVersion string } +func (handler GetRootHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + func (handler GetRootHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { var res horizon.Root templates := map[string]string{ diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index 80a0274442..62fe33c622 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -3,11 +3,12 @@ package actions import ( "context" "encoding/hex" - "github.com/stellar/go/network" - "github.com/stellar/go/support/errors" "mime" "net/http" + "github.com/stellar/go/network" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" @@ -30,6 +31,10 @@ type SubmitTransactionHandler struct { SkipTxMeta bool } +func (handler SubmitTransactionHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + type envelopeInfo struct { hash string innerHash string diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index f16446dd0b..03cb0b8de2 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -12,6 +12,13 @@ import ( "github.com/stellar/go/support/render/problem" ) +var coreStatusToHTTPStatus = map[string]int{ + proto.TXStatusPending: http.StatusCreated, + proto.TXStatusDuplicate: http.StatusConflict, + proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, + proto.TXStatusError: http.StatusBadRequest, +} + type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool @@ -19,6 +26,14 @@ type AsyncSubmitTransactionHandler struct { CoreStateGetter } +func (handler AsyncSubmitTransactionHandler) HttpStatus(resp interface{}) int { + statusCode := http.StatusOK + if asyncTxSubResponse, ok := resp.(horizon.AsyncTransactionSubmissionResponse); ok { + statusCode = coreStatusToHTTPStatus[asyncTxSubResponse.TxStatus] + } + return statusCode +} + func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { logger := log.Ctx(r.Context()) diff --git a/services/horizon/internal/actions/trade.go b/services/horizon/internal/actions/trade.go index d6bf415424..17fc79ca13 100644 --- a/services/horizon/internal/actions/trade.go +++ b/services/horizon/internal/actions/trade.go @@ -280,6 +280,10 @@ type GetTradeAggregationsHandler struct { CoreStateGetter } +func (handler GetTradeAggregationsHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // GetResource returns a page of trade aggregations func (handler GetTradeAggregationsHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/actions/transaction.go b/services/horizon/internal/actions/transaction.go index 6903d5db2f..de75c8be93 100644 --- a/services/horizon/internal/actions/transaction.go +++ b/services/horizon/internal/actions/transaction.go @@ -26,6 +26,10 @@ type GetTransactionByHashHandler struct { SkipTxMeta bool } +func (handler GetTransactionByHashHandler) HttpStatus(resp interface{}) int { + return http.StatusOK +} + // GetResource returns a transaction page. func (handler GetTransactionByHashHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/httpx/handler.go b/services/horizon/internal/httpx/handler.go index e17ecb987d..2085b2c60a 100644 --- a/services/horizon/internal/httpx/handler.go +++ b/services/horizon/internal/httpx/handler.go @@ -23,6 +23,7 @@ type objectAction interface { w actions.HeaderWriter, r *http.Request, ) (interface{}, error) + HttpStatus(resp interface{}) int } type ObjectActionHandler struct { @@ -41,8 +42,10 @@ func (handler ObjectActionHandler) ServeHTTP( return } - httpjson.Render( + statusCode := handler.Action.HttpStatus(response) + httpjson.RenderStatus( w, + statusCode, response, httpjson.HALJSON, ) diff --git a/support/render/httpjson/io.go b/support/render/httpjson/io.go index 9b08cb4ea0..2071629462 100644 --- a/support/render/httpjson/io.go +++ b/support/render/httpjson/io.go @@ -2,8 +2,6 @@ package httpjson import ( "encoding/json" - "github.com/stellar/go/protocols/horizon" - proto "github.com/stellar/go/protocols/stellarcore" "net/http" "github.com/stellar/go/support/errors" @@ -11,13 +9,6 @@ import ( type contentType int -var coreStatusToHTTPStatus = map[string]int{ - proto.TXStatusPending: http.StatusCreated, - proto.TXStatusDuplicate: http.StatusConflict, - proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, - proto.TXStatusError: http.StatusBadRequest, -} - const ( JSON contentType = iota HALJSON @@ -36,12 +27,7 @@ func renderToString(data interface{}, pretty bool) ([]byte, error) { // Render write data to w, after marshaling to json. The response header is // set based on cType. func Render(w http.ResponseWriter, data interface{}, cType contentType) { - statusCode := http.StatusOK - if asyncTxSubResponse, ok := data.(horizon.AsyncTransactionSubmissionResponse); ok { - statusCode = coreStatusToHTTPStatus[asyncTxSubResponse.TxStatus] - } - - RenderStatus(w, statusCode, data, cType) + RenderStatus(w, http.StatusOK, data, cType) } // RenderStatus write data to w, after marshaling to json. From 2114193f3c6d20215489540ddee6dde8be43b873 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 3 Apr 2024 15:25:43 -0400 Subject: [PATCH 43/68] Remove not needed metrics --- clients/stellarcore/metrics_client.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 7e1759ee43..bc36785308 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -21,18 +21,6 @@ type clientWithMetrics struct { // SubmissionDuration exposes timing metrics about the rate and latency of // submissions to stellar-core SubmissionDuration *prometheus.SummaryVec - - // V0TransactionsCounter tracks the rate of v0 transaction envelopes that - // have been submitted to this process - V0TransactionsCounter *prometheus.CounterVec - - // V1TransactionsCounter tracks the rate of v1 transaction envelopes that - // have been submitted to this process - V1TransactionsCounter *prometheus.CounterVec - - // FeeBumpTransactionsCounter tracks the rate of fee bump transaction envelopes that - // have been submitted to this process - FeeBumpTransactionsCounter *prometheus.CounterVec } } @@ -82,10 +70,7 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe return &clientWithMetrics{ CoreClient: client, TxSubMetrics: struct { - SubmissionDuration *prometheus.SummaryVec - V0TransactionsCounter *prometheus.CounterVec - V1TransactionsCounter *prometheus.CounterVec - FeeBumpTransactionsCounter *prometheus.CounterVec + SubmissionDuration *prometheus.SummaryVec }{ SubmissionDuration: submissionDuration, }, From 3aca364adad068b8af086c3ff00b5c69354e41c7 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Wed, 10 Apr 2024 10:12:14 -0400 Subject: [PATCH 44/68] Remove version --- .../internal/actions/submit_transaction_async_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index bf987eba1a..ca96d57fda 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -2,20 +2,22 @@ package actions import ( "context" - stellarcore "github.com/stellar/go/clients/stellarcore" - "github.com/stellar/go/protocols/horizon" "net/http" "net/http/httptest" "net/url" "strings" "testing" + stellarcore "github.com/stellar/go/clients/stellarcore" + "github.com/stellar/go/protocols/horizon" + + "github.com/stretchr/testify/assert" + "github.com/stellar/go/network" proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/services/horizon/internal/corestate" "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/problem" - "github.com/stretchr/testify/assert" ) const ( @@ -29,7 +31,7 @@ func createRequest() *http.Request { request, _ := http.NewRequest( "POST", - "http://localhost:8000/v2/transactions_async", + "http://localhost:8000/transactions_async", strings.NewReader(form.Encode()), ) request.Header.Add("Content-Type", "application/x-www-form-urlencoded") From 5c0089e1e807c223b3644fc4064431b496cd8470 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 09:48:59 -0400 Subject: [PATCH 45/68] add error in extras --- services/horizon/internal/actions/submit_transaction_async.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 03cb0b8de2..77c6e3a8a9 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -72,6 +72,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http "convenience.", Extras: map[string]interface{}{ "envelope_xdr": raw, + "error": err, }, } } From 9517e760e9ffd7b61aad0b5ec9233ac1bfcbe3e6 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 09:52:04 -0400 Subject: [PATCH 46/68] Resolve merge conflicts --- services/horizon/internal/app.go | 2 +- services/horizon/internal/httpx/router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/app.go b/services/horizon/internal/app.go index 7274f7ebfd..843232beba 100644 --- a/services/horizon/internal/app.go +++ b/services/horizon/internal/app.go @@ -554,7 +554,7 @@ func (a *App) init() error { HorizonVersion: a.horizonVersion, FriendbotURL: a.config.FriendbotURL, DisableTxSub: a.config.DisableTxSub, - StellarCoreURL: a.config.StellarCoreURL, + StellarCoreURL: a.config.StellarCoreURL, HealthCheck: healthCheck{ session: a.historyQ.SessionInterface, ctx: a.ctx, diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 787af81fe6..ba5af85b51 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -54,7 +54,7 @@ type RouterConfig struct { HealthCheck http.Handler DisableTxSub bool SkipTxMeta bool - StellarCoreURL string + StellarCoreURL string } type Router struct { From 122a087f7340c11ef5a3d45d3d07123433e53997 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 10:59:57 -0400 Subject: [PATCH 47/68] Add TODO for problem response --- services/horizon/internal/actions/submit_transaction_async.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 77c6e3a8a9..dd5a8b8b5c 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -35,6 +35,7 @@ func (handler AsyncSubmitTransactionHandler) HttpStatus(resp interface{}) int { } func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { + // TODO: Move the problem responses to a separate file as constants or a function. logger := log.Ctx(r.Context()) if err := validateBodyType(r); err != nil { From c571dd5f57f9eb2f6b29ede187e2e7ee25e423d1 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 11:06:59 -0400 Subject: [PATCH 48/68] Adding and removing logging statements --- .../horizon/internal/actions/submit_transaction_async.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index dd5a8b8b5c..2ba2b47269 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -101,6 +101,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } if resp.IsException() { + logger.WithField("envelope_xdr", raw).WithError(errors.Errorf(resp.Exception)).Error("Transaction submission exception from stellar-core") return nil, &problem.P{ Type: "transaction_submission_exception", Title: "Transaction Submission Exception", @@ -124,12 +125,6 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http } if resp.Status == proto.TXStatusError { - logger.WithFields(log.F{ - "envelope_xdr": raw, - "error_xdr": resp.Error, - "status": resp.Status, - "hash": info.hash, - }).Error("Transaction submission to stellar-core resulted in ERROR status") response.ErrorResultXDR = resp.Error } From 7e3e495098fcb516b3c443ffb7b06669d398fd23 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 14:02:06 -0400 Subject: [PATCH 49/68] Move interface to async handler file --- clients/stellarcore/metrics_client.go | 12 ++++-------- .../internal/actions/submit_transaction_async.go | 9 +++++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index bc36785308..c752c76e8d 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -10,11 +10,7 @@ import ( "github.com/stellar/go/xdr" ) -type ClientWithMetrics interface { - SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) -} - -type clientWithMetrics struct { +type ClientWithMetrics struct { CoreClient Client TxSubMetrics struct { @@ -24,7 +20,7 @@ type clientWithMetrics struct { } } -func (c *clientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { +func (c ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { startTime := time.Now() response, err := c.CoreClient.SubmitTransaction(ctx, rawTx) c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) @@ -32,7 +28,7 @@ func (c *clientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, return response, err } -func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { +func (c ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { label := prometheus.Labels{} if err != nil { label["status"] = "request_error" @@ -67,7 +63,7 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe submissionDuration, ) - return &clientWithMetrics{ + return ClientWithMetrics{ CoreClient: client, TxSubMetrics: struct { SubmissionDuration *prometheus.SummaryVec diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 2ba2b47269..932b4194bd 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -1,15 +1,16 @@ package actions import ( + "context" "net/http" - "github.com/stellar/go/clients/stellarcore" "github.com/stellar/go/protocols/horizon" proto "github.com/stellar/go/protocols/stellarcore" hProblem "github.com/stellar/go/services/horizon/internal/render/problem" "github.com/stellar/go/support/errors" "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/problem" + "github.com/stellar/go/xdr" ) var coreStatusToHTTPStatus = map[string]int{ @@ -19,10 +20,14 @@ var coreStatusToHTTPStatus = map[string]int{ proto.TXStatusError: http.StatusBadRequest, } +type CoreClient interface { + SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) +} + type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - ClientWithMetrics stellarcore.ClientWithMetrics + ClientWithMetrics CoreClient CoreStateGetter } From 03b2c663ac4191250d1134211fb1782e7dd49e4b Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 17:14:19 -0400 Subject: [PATCH 50/68] change httpstatus interface definition --- protocols/horizon/main.go | 13 +++++++++++++ .../horizon/internal/actions/claimable_balance.go | 4 ---- services/horizon/internal/actions/fee_stats.go | 4 ---- services/horizon/internal/actions/ledger.go | 4 ---- .../horizon/internal/actions/liquidity_pool.go | 4 ---- services/horizon/internal/actions/offer.go | 4 ---- services/horizon/internal/actions/operation.go | 4 ---- services/horizon/internal/actions/path.go | 8 -------- services/horizon/internal/actions/root.go | 4 ---- .../internal/actions/submit_transaction.go | 4 ---- .../internal/actions/submit_transaction_async.go | 15 --------------- services/horizon/internal/actions/trade.go | 4 ---- services/horizon/internal/actions/transaction.go | 4 ---- services/horizon/internal/httpx/handler.go | 10 ++++++++-- 14 files changed, 21 insertions(+), 65 deletions(-) diff --git a/protocols/horizon/main.go b/protocols/horizon/main.go index 011fe13565..dd9cc7814f 100644 --- a/protocols/horizon/main.go +++ b/protocols/horizon/main.go @@ -8,10 +8,12 @@ import ( "fmt" "math" "math/big" + "net/http" "strconv" "time" "github.com/stellar/go/protocols/horizon/base" + proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/strkey" "github.com/stellar/go/support/errors" "github.com/stellar/go/support/render/hal" @@ -29,6 +31,13 @@ var KeyTypeNames = map[strkey.VersionByte]string{ strkey.VersionByteSignedPayload: "ed25519_signed_payload", } +var coreStatusToHTTPStatus = map[string]int{ + proto.TXStatusPending: http.StatusCreated, + proto.TXStatusDuplicate: http.StatusConflict, + proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, + proto.TXStatusError: http.StatusBadRequest, +} + // Account is the summary of an account type Account struct { Links struct { @@ -583,6 +592,10 @@ type AsyncTransactionSubmissionResponse struct { Hash string `json:"hash"` } +func (response AsyncTransactionSubmissionResponse) GetStatus() int { + return coreStatusToHTTPStatus[response.TxStatus] +} + // MarshalJSON implements a custom marshaler for Transaction. // The memo field should be omitted if and only if the // memo_type is "none". diff --git a/services/horizon/internal/actions/claimable_balance.go b/services/horizon/internal/actions/claimable_balance.go index a1a2876a3f..dbfa02c298 100644 --- a/services/horizon/internal/actions/claimable_balance.go +++ b/services/horizon/internal/actions/claimable_balance.go @@ -20,10 +20,6 @@ import ( // GetClaimableBalanceByIDHandler is the action handler for all end-points returning a claimable balance. type GetClaimableBalanceByIDHandler struct{} -func (handler GetClaimableBalanceByIDHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // ClaimableBalanceQuery query struct for claimables_balances/id end-point type ClaimableBalanceQuery struct { ID string `schema:"id" valid:"claimableBalanceID,required"` diff --git a/services/horizon/internal/actions/fee_stats.go b/services/horizon/internal/actions/fee_stats.go index 56b9207288..5a51d01c4b 100644 --- a/services/horizon/internal/actions/fee_stats.go +++ b/services/horizon/internal/actions/fee_stats.go @@ -12,10 +12,6 @@ import ( type FeeStatsHandler struct { } -func (handler FeeStatsHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // GetResource fee stats resource func (handler FeeStatsHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { feeStats := horizon.FeeStats{} diff --git a/services/horizon/internal/actions/ledger.go b/services/horizon/internal/actions/ledger.go index 5fadf15ad6..37fddb5bd9 100644 --- a/services/horizon/internal/actions/ledger.go +++ b/services/horizon/internal/actions/ledger.go @@ -59,10 +59,6 @@ type GetLedgerByIDHandler struct { LedgerState *ledger.State } -func (handler GetLedgerByIDHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - func (handler GetLedgerByIDHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { qp := LedgerByIDQuery{} err := getParams(&qp, r) diff --git a/services/horizon/internal/actions/liquidity_pool.go b/services/horizon/internal/actions/liquidity_pool.go index eaacf485e6..bb1aedd882 100644 --- a/services/horizon/internal/actions/liquidity_pool.go +++ b/services/horizon/internal/actions/liquidity_pool.go @@ -19,10 +19,6 @@ import ( // GetLiquidityPoolByIDHandler is the action handler for all end-points returning a liquidity pool. type GetLiquidityPoolByIDHandler struct{} -func (handler GetLiquidityPoolByIDHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // LiquidityPoolQuery query struct for liquidity_pools/id endpoint type LiquidityPoolQuery struct { ID string `schema:"liquidity_pool_id" valid:"sha256"` diff --git a/services/horizon/internal/actions/offer.go b/services/horizon/internal/actions/offer.go index 24d5bc6b71..c5de593c4b 100644 --- a/services/horizon/internal/actions/offer.go +++ b/services/horizon/internal/actions/offer.go @@ -21,10 +21,6 @@ type OfferByIDQuery struct { // GetOfferByID is the action handler for the /offers/{id} endpoint type GetOfferByID struct{} -func (handler GetOfferByID) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // GetResource returns an offer by id. func (handler GetOfferByID) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/actions/operation.go b/services/horizon/internal/actions/operation.go index 09ab346a11..f59191aee0 100644 --- a/services/horizon/internal/actions/operation.go +++ b/services/horizon/internal/actions/operation.go @@ -136,10 +136,6 @@ type GetOperationByIDHandler struct { SkipTxMeta bool } -func (handler GetOperationByIDHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // OperationQuery query struct for operation/id end-point type OperationQuery struct { LedgerState *ledger.State `valid:"-"` diff --git a/services/horizon/internal/actions/path.go b/services/horizon/internal/actions/path.go index 2d25053eca..fca06fa970 100644 --- a/services/horizon/internal/actions/path.go +++ b/services/horizon/internal/actions/path.go @@ -29,10 +29,6 @@ type FindPathsHandler struct { PathFinder paths.Finder } -func (handler FindPathsHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // StrictReceivePathsQuery query struct for paths/strict-send end-point type StrictReceivePathsQuery struct { SourceAssets string `schema:"source_assets" valid:"-"` @@ -212,10 +208,6 @@ type FindFixedPathsHandler struct { PathFinder paths.Finder } -func (handler FindFixedPathsHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // DestinationAssetsOrDestinationAccountProblem custom error where destination asserts or accounts are required var DestinationAssetsOrDestinationAccountProblem = problem.P{ Type: "bad_request", diff --git a/services/horizon/internal/actions/root.go b/services/horizon/internal/actions/root.go index 11b1782953..42bbc1b3b3 100644 --- a/services/horizon/internal/actions/root.go +++ b/services/horizon/internal/actions/root.go @@ -17,10 +17,6 @@ type GetRootHandler struct { HorizonVersion string } -func (handler GetRootHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - func (handler GetRootHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { var res horizon.Root templates := map[string]string{ diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index 62fe33c622..23a04b130b 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -31,10 +31,6 @@ type SubmitTransactionHandler struct { SkipTxMeta bool } -func (handler SubmitTransactionHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - type envelopeInfo struct { hash string innerHash string diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 932b4194bd..2f86872130 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -13,13 +13,6 @@ import ( "github.com/stellar/go/xdr" ) -var coreStatusToHTTPStatus = map[string]int{ - proto.TXStatusPending: http.StatusCreated, - proto.TXStatusDuplicate: http.StatusConflict, - proto.TXStatusTryAgainLater: http.StatusServiceUnavailable, - proto.TXStatusError: http.StatusBadRequest, -} - type CoreClient interface { SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) } @@ -31,14 +24,6 @@ type AsyncSubmitTransactionHandler struct { CoreStateGetter } -func (handler AsyncSubmitTransactionHandler) HttpStatus(resp interface{}) int { - statusCode := http.StatusOK - if asyncTxSubResponse, ok := resp.(horizon.AsyncTransactionSubmissionResponse); ok { - statusCode = coreStatusToHTTPStatus[asyncTxSubResponse.TxStatus] - } - return statusCode -} - func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http.Request) (interface{}, error) { // TODO: Move the problem responses to a separate file as constants or a function. logger := log.Ctx(r.Context()) diff --git a/services/horizon/internal/actions/trade.go b/services/horizon/internal/actions/trade.go index 17fc79ca13..d6bf415424 100644 --- a/services/horizon/internal/actions/trade.go +++ b/services/horizon/internal/actions/trade.go @@ -280,10 +280,6 @@ type GetTradeAggregationsHandler struct { CoreStateGetter } -func (handler GetTradeAggregationsHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // GetResource returns a page of trade aggregations func (handler GetTradeAggregationsHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/actions/transaction.go b/services/horizon/internal/actions/transaction.go index de75c8be93..6903d5db2f 100644 --- a/services/horizon/internal/actions/transaction.go +++ b/services/horizon/internal/actions/transaction.go @@ -26,10 +26,6 @@ type GetTransactionByHashHandler struct { SkipTxMeta bool } -func (handler GetTransactionByHashHandler) HttpStatus(resp interface{}) int { - return http.StatusOK -} - // GetResource returns a transaction page. func (handler GetTransactionByHashHandler) GetResource(w HeaderWriter, r *http.Request) (interface{}, error) { ctx := r.Context() diff --git a/services/horizon/internal/httpx/handler.go b/services/horizon/internal/httpx/handler.go index 2085b2c60a..ade5742566 100644 --- a/services/horizon/internal/httpx/handler.go +++ b/services/horizon/internal/httpx/handler.go @@ -23,7 +23,10 @@ type objectAction interface { w actions.HeaderWriter, r *http.Request, ) (interface{}, error) - HttpStatus(resp interface{}) int +} + +type HttpResponse interface { + GetStatus() int } type ObjectActionHandler struct { @@ -42,7 +45,10 @@ func (handler ObjectActionHandler) ServeHTTP( return } - statusCode := handler.Action.HttpStatus(response) + statusCode := http.StatusOK + if httpResponse, ok := response.(HttpResponse); ok { + statusCode = httpResponse.GetStatus() + } httpjson.RenderStatus( w, statusCode, From c869205707249eab06e72b040a3a43e5470699bc Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Fri, 19 Apr 2024 17:17:36 -0400 Subject: [PATCH 51/68] Add deleted files back --- support/render/hal/handler.go | 16 ++++++++++++++++ support/render/hal/io.go | 12 ++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 support/render/hal/handler.go create mode 100644 support/render/hal/io.go diff --git a/support/render/hal/handler.go b/support/render/hal/handler.go new file mode 100644 index 0000000000..2eaa4b1e2c --- /dev/null +++ b/support/render/hal/handler.go @@ -0,0 +1,16 @@ +package hal + +import ( + "context" + "net/http" + + "github.com/stellar/go/support/render/httpjson" +) + +func Handler(fn, param interface{}) (http.Handler, error) { + return httpjson.Handler(fn, param, httpjson.HALJSON) +} + +func ExecuteFunc(ctx context.Context, fn, param interface{}) (interface{}, bool, error) { + return httpjson.ExecuteFunc(ctx, fn, param, httpjson.HALJSON) +} diff --git a/support/render/hal/io.go b/support/render/hal/io.go new file mode 100644 index 0000000000..2b1bbc71b7 --- /dev/null +++ b/support/render/hal/io.go @@ -0,0 +1,12 @@ +package hal + +import ( + "net/http" + + "github.com/stellar/go/support/render/httpjson" +) + +// Render write data to w, after marshaling to json +func Render(w http.ResponseWriter, data interface{}) { + httpjson.Render(w, data, httpjson.HALJSON) +} From 71893ba13c41d43deb3de16ab97c44a7de7610a1 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 22 Apr 2024 10:43:18 -0400 Subject: [PATCH 52/68] Revert friendbot change --- services/friendbot/internal/friendbot_handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/friendbot/internal/friendbot_handler.go b/services/friendbot/internal/friendbot_handler.go index e56da2c72e..9e6f3f8d74 100644 --- a/services/friendbot/internal/friendbot_handler.go +++ b/services/friendbot/internal/friendbot_handler.go @@ -1,12 +1,12 @@ package internal import ( - "github.com/stellar/go/support/render/httpjson" "net/http" "net/url" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/strkey" + "github.com/stellar/go/support/render/hal" "github.com/stellar/go/support/render/problem" ) @@ -23,7 +23,7 @@ func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) return } - httpjson.Render(w, *result, httpjson.HALJSON) + hal.Render(w, *result) } // doHandle is just a convenience method that returns the object to be rendered From be59a2c3170a93a7841d17c49d492621b6696007 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 22 Apr 2024 12:17:30 -0400 Subject: [PATCH 53/68] Add test for getting pending tx --- .../internal/integration/txsub_async_test.go | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/services/horizon/internal/integration/txsub_async_test.go b/services/horizon/internal/integration/txsub_async_test.go index 38b03ea88c..5286f143ba 100644 --- a/services/horizon/internal/integration/txsub_async_test.go +++ b/services/horizon/internal/integration/txsub_async_test.go @@ -1,15 +1,31 @@ package integration import ( - "github.com/stellar/go/protocols/horizon" - "github.com/stellar/go/services/horizon/internal/test/integration" - "github.com/stellar/go/txnbuild" - "github.com/stretchr/testify/assert" "io" "net/http" "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/services/horizon/internal/test/integration" + "github.com/stellar/go/support/errors" + "github.com/stellar/go/txnbuild" ) +func getTransaction(t *testing.T, itest *integration.Test, hash string) error { + for i := 0; i < 60; i++ { + _, err := itest.Client().TransactionDetail(hash) + if assert.NoError(t, err) { + return nil + } + + time.Sleep(time.Second) + } + return errors.New("transaction not found") +} + func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { itest := integration.NewTest(t, integration.Config{}) master := itest.Master() @@ -38,6 +54,9 @@ func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { TxStatus: "PENDING", Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) + + err = getTransaction(t, itest, txResp.Hash) + assert.NoError(t, err) } func TestAsyncTxSub_SubmissionError(t *testing.T) { From d632a67619883cb89012587ff5352cc055132b33 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Mon, 22 Apr 2024 13:52:57 -0400 Subject: [PATCH 54/68] Fix failing test --- .../internal/integration/txsub_async_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/services/horizon/internal/integration/txsub_async_test.go b/services/horizon/internal/integration/txsub_async_test.go index 5286f143ba..2d16e4d4ea 100644 --- a/services/horizon/internal/integration/txsub_async_test.go +++ b/services/horizon/internal/integration/txsub_async_test.go @@ -8,20 +8,22 @@ import ( "github.com/stretchr/testify/assert" + "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/services/horizon/internal/test/integration" "github.com/stellar/go/support/errors" "github.com/stellar/go/txnbuild" ) -func getTransaction(t *testing.T, itest *integration.Test, hash string) error { +func getTransaction(client *horizonclient.Client, hash string) error { for i := 0; i < 60; i++ { - _, err := itest.Client().TransactionDetail(hash) - if assert.NoError(t, err) { - return nil + _, err := client.TransactionDetail(hash) + if err != nil { + time.Sleep(time.Second) + continue } - time.Sleep(time.Second) + return nil } return errors.New("transaction not found") } @@ -55,7 +57,7 @@ func TestAsyncTxSub_SuccessfulSubmission(t *testing.T) { Hash: "6cbb7f714bd08cea7c30cab7818a35c510cbbfc0a6aa06172a1e94146ecf0165", }) - err = getTransaction(t, itest, txResp.Hash) + err = getTransaction(itest.Client(), txResp.Hash) assert.NoError(t, err) } From d31a01e0219bcfe0f8e66429395f5649362260da Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 10:47:07 -0400 Subject: [PATCH 55/68] remove metrics struct and make vars private --- clients/stellarcore/metrics_client.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index c752c76e8d..00b4d74e05 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -11,18 +11,16 @@ import ( ) type ClientWithMetrics struct { - CoreClient Client + coreClient Client - TxSubMetrics struct { - // SubmissionDuration exposes timing metrics about the rate and latency of - // submissions to stellar-core - SubmissionDuration *prometheus.SummaryVec - } + // submissionDuration exposes timing metrics about the rate and latency of + // submissions to stellar-core + submissionDuration *prometheus.SummaryVec } func (c ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { startTime := time.Now() - response, err := c.CoreClient.SubmitTransaction(ctx, rawTx) + response, err := c.coreClient.SubmitTransaction(ctx, rawTx) c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) return response, err @@ -47,7 +45,7 @@ func (c ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.Tra label["envelope_type"] = "fee-bump" } - c.TxSubMetrics.SubmissionDuration.With(label).Observe(duration) + c.submissionDuration.With(label).Observe(duration) } func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { @@ -64,11 +62,7 @@ func NewClientWithMetrics(client Client, registry *prometheus.Registry, promethe ) return ClientWithMetrics{ - CoreClient: client, - TxSubMetrics: struct { - SubmissionDuration *prometheus.SummaryVec - }{ - SubmissionDuration: submissionDuration, - }, + coreClient: client, + submissionDuration: submissionDuration, } } From 62d02d138bd266c66879fb718e4fa7ebc1a7bef9 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 11:23:40 -0400 Subject: [PATCH 56/68] pass only rawTx string --- clients/stellarcore/metrics_client.go | 8 +++++++- clients/stellarcore/mocks.go | 15 ++++++--------- .../internal/actions/submit_transaction_async.go | 5 ++--- services/horizon/internal/txsub/submitter.go | 8 +++++--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 00b4d74e05..612cfed59b 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -18,7 +18,13 @@ type ClientWithMetrics struct { submissionDuration *prometheus.SummaryVec } -func (c ClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { +func (c ClientWithMetrics) SubmitTx(ctx context.Context, rawTx string) (*proto.TXResponse, error) { + var envelope xdr.TransactionEnvelope + err := xdr.SafeUnmarshalBase64(rawTx, &envelope) + if err != nil { + return &proto.TXResponse{}, err + } + startTime := time.Now() response, err := c.coreClient.SubmitTransaction(ctx, rawTx) c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go index 0f2392aa30..cce35e3477 100644 --- a/clients/stellarcore/mocks.go +++ b/clients/stellarcore/mocks.go @@ -2,21 +2,18 @@ package stellarcore import ( "context" - proto "github.com/stellar/go/protocols/stellarcore" - "github.com/stellar/go/xdr" + "github.com/stretchr/testify/mock" + + proto "github.com/stellar/go/protocols/stellarcore" ) type MockClientWithMetrics struct { mock.Mock } -// SubmitTransaction mocks the SubmitTransaction method -func (m *MockClientWithMetrics) SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (*proto.TXResponse, error) { - args := m.Called(ctx, rawTx, envelope) +// SubmitTx mocks the SubmitTransaction method +func (m *MockClientWithMetrics) SubmitTx(ctx context.Context, rawTx string) (*proto.TXResponse, error) { + args := m.Called(ctx, rawTx) return args.Get(0).(*proto.TXResponse), args.Error(1) } - -func (m *MockClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { - m.Called(duration, envelope, response, err) -} diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index 2f86872130..c2f410a6ed 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -10,11 +10,10 @@ import ( "github.com/stellar/go/support/errors" "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/problem" - "github.com/stellar/go/xdr" ) type CoreClient interface { - SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error) + SubmitTx(ctx context.Context, rawTx string) (resp *proto.TXResponse, err error) } type AsyncSubmitTransactionHandler struct { @@ -73,7 +72,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, hProblem.StaleHistory } - resp, err := handler.ClientWithMetrics.SubmitTransaction(r.Context(), info.raw, info.parsed) + resp, err := handler.ClientWithMetrics.SubmitTx(r.Context(), raw) if err != nil { return nil, &problem.P{ Type: "transaction_submission_failed", diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index 1ac78754a2..af67f132a3 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -2,11 +2,13 @@ package txsub import ( "context" - "github.com/prometheus/client_golang/prometheus" - "github.com/stellar/go/xdr" "net/http" "time" + "github.com/prometheus/client_golang/prometheus" + + "github.com/stellar/go/xdr" + "github.com/stellar/go/clients/stellarcore" proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/support/errors" @@ -46,7 +48,7 @@ func (sub *submitter) Submit(ctx context.Context, rawTx string, envelope xdr.Tra }).Info("Submitter result") }() - cresp, err := sub.StellarCore.SubmitTransaction(ctx, rawTx, envelope) + cresp, err := sub.StellarCore.SubmitTx(ctx, rawTx) if err != nil { result.Err = errors.Wrap(err, "failed to submit") return From 32c71f841fb5902c926b25ecf61f31163e3f458d Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 11:27:12 -0400 Subject: [PATCH 57/68] Move mock to test file --- clients/stellarcore/mocks.go | 19 ---------- .../actions/submit_transaction_async_test.go | 38 ++++++++++--------- 2 files changed, 20 insertions(+), 37 deletions(-) delete mode 100644 clients/stellarcore/mocks.go diff --git a/clients/stellarcore/mocks.go b/clients/stellarcore/mocks.go deleted file mode 100644 index cce35e3477..0000000000 --- a/clients/stellarcore/mocks.go +++ /dev/null @@ -1,19 +0,0 @@ -package stellarcore - -import ( - "context" - - "github.com/stretchr/testify/mock" - - proto "github.com/stellar/go/protocols/stellarcore" -) - -type MockClientWithMetrics struct { - mock.Mock -} - -// SubmitTx mocks the SubmitTransaction method -func (m *MockClientWithMetrics) SubmitTx(ctx context.Context, rawTx string) (*proto.TXResponse, error) { - args := m.Called(ctx, rawTx) - return args.Get(0).(*proto.TXResponse), args.Error(1) -} diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index ca96d57fda..a741705a7f 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -8,7 +8,8 @@ import ( "strings" "testing" - stellarcore "github.com/stellar/go/clients/stellarcore" + "github.com/stretchr/testify/mock" + "github.com/stellar/go/protocols/horizon" "github.com/stretchr/testify/assert" @@ -25,6 +26,16 @@ const ( TxHash = "3389e9f0f1a65f19736cacf544c2e825313e8447f569233bb8db39aa607c8889" ) +type MockClientWithMetrics struct { + mock.Mock +} + +// SubmitTx mocks the SubmitTransaction method +func (m *MockClientWithMetrics) SubmitTx(ctx context.Context, rawTx string) (*proto.TXResponse, error) { + args := m.Called(ctx, rawTx) + return args.Get(0).(*proto.TXResponse), args.Error(1) +} + func createRequest() *http.Request { form := url.Values{} form.Set("tx", TxXDR) @@ -91,11 +102,8 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) - assert.NoError(t, err) - - MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(&proto.TXResponse{}, errors.Errorf("submission error")) + MockClientWithMetrics := &MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTx", context.Background(), TxXDR).Return(&proto.TXResponse{}, errors.Errorf("submission error")) handler := AsyncSubmitTransactionHandler{ CoreStateGetter: coreStateGetter, @@ -106,7 +114,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionFailed(t *testing.T) request := createRequest() w := httptest.NewRecorder() - _, err = handler.GetResource(w, request) + _, err := handler.GetResource(w, request) assert.NotNil(t, err) assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) @@ -118,11 +126,8 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) - assert.NoError(t, err) - - MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(&proto.TXResponse{ + MockClientWithMetrics := &MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTx", context.Background(), TxXDR).Return(&proto.TXResponse{ Exception: "some-exception", }, nil) @@ -135,7 +140,7 @@ func TestAsyncSubmitTransactionHandler_TransactionSubmissionException(t *testing request := createRequest() w := httptest.NewRecorder() - _, err = handler.GetResource(w, request) + _, err := handler.GetResource(w, request) assert.NotNil(t, err) assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) @@ -147,9 +152,6 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { coreStateGetter := new(coreStateGetterMock) coreStateGetter.On("GetCoreState").Return(corestate.State{Synced: true}) - info, err := extractEnvelopeInfo(TxXDR, network.TestNetworkPassphrase) - assert.NoError(t, err) - successCases := []struct { mockCoreResponse *proto.TXResponse expectedResponse horizon.AsyncTransactionSubmissionResponse @@ -197,8 +199,8 @@ func TestAsyncSubmitTransactionHandler_TransactionStatusResponse(t *testing.T) { } for _, testCase := range successCases { - MockClientWithMetrics := &stellarcore.MockClientWithMetrics{} - MockClientWithMetrics.On("SubmitTransaction", context.Background(), TxXDR, info.parsed).Return(testCase.mockCoreResponse, nil) + MockClientWithMetrics := &MockClientWithMetrics{} + MockClientWithMetrics.On("SubmitTx", context.Background(), TxXDR).Return(testCase.mockCoreResponse, nil) handler := AsyncSubmitTransactionHandler{ NetworkPassphrase: network.PublicNetworkPassphrase, From d3b8c1e02e8ac7ee95ee84bddc6298afc860b61c Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 11:28:54 -0400 Subject: [PATCH 58/68] Make core client private --- services/horizon/internal/actions/submit_transaction_async.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index c2f410a6ed..ff1d8db4a9 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -12,14 +12,14 @@ import ( "github.com/stellar/go/support/render/problem" ) -type CoreClient interface { +type coreClient interface { SubmitTx(ctx context.Context, rawTx string) (resp *proto.TXResponse, err error) } type AsyncSubmitTransactionHandler struct { NetworkPassphrase string DisableTxSub bool - ClientWithMetrics CoreClient + ClientWithMetrics coreClient CoreStateGetter } From 573bdb668c5a44b4f9af7f08a075e9b5d041ae15 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 13:16:13 -0400 Subject: [PATCH 59/68] Remove UpdateTxSubMetrics func --- clients/stellarcore/metrics_client.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/clients/stellarcore/metrics_client.go b/clients/stellarcore/metrics_client.go index 612cfed59b..2353905af7 100644 --- a/clients/stellarcore/metrics_client.go +++ b/clients/stellarcore/metrics_client.go @@ -10,6 +10,12 @@ import ( "github.com/stellar/go/xdr" ) +var envelopeTypeToLabel = map[xdr.EnvelopeType]string{ + xdr.EnvelopeTypeEnvelopeTypeTxV0: "v0", + xdr.EnvelopeTypeEnvelopeTypeTx: "v1", + xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: "fee_bump", +} + type ClientWithMetrics struct { coreClient Client @@ -27,12 +33,8 @@ func (c ClientWithMetrics) SubmitTx(ctx context.Context, rawTx string) (*proto.T startTime := time.Now() response, err := c.coreClient.SubmitTransaction(ctx, rawTx) - c.updateTxSubMetrics(time.Since(startTime).Seconds(), envelope, response, err) - - return response, err -} + duration := time.Since(startTime).Seconds() -func (c ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) { label := prometheus.Labels{} if err != nil { label["status"] = "request_error" @@ -42,16 +44,10 @@ func (c ClientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.Tra label["status"] = response.Status } - switch envelope.Type { - case xdr.EnvelopeTypeEnvelopeTypeTxV0: - label["envelope_type"] = "v0" - case xdr.EnvelopeTypeEnvelopeTypeTx: - label["envelope_type"] = "v1" - case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump: - label["envelope_type"] = "fee-bump" - } - + label["envelope_type"] = envelopeTypeToLabel[envelope.Type] c.submissionDuration.With(label).Observe(duration) + + return response, err } func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics { From 00985dbc5a541543819894a93bcde261cb45a2b3 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 13:19:41 -0400 Subject: [PATCH 60/68] Change http status for DISABLE_TX_SUB --- services/horizon/internal/actions/submit_transaction.go | 2 +- services/horizon/internal/actions/submit_transaction_async.go | 2 +- services/horizon/internal/actions/submit_transaction_test.go | 2 +- services/horizon/internal/httpx/static/txsub_async_oapi.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index 23a04b130b..00049a9172 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -148,7 +148,7 @@ func (handler SubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Requ return nil, &problem.P{ Type: "transaction_submission_disabled", Title: "Transaction Submission Disabled", - Status: http.StatusMethodNotAllowed, + Status: http.StatusForbidden, Detail: "Transaction submission has been disabled for Horizon. " + "To enable it again, remove env variable DISABLE_TX_SUB.", Extras: map[string]interface{}{}, diff --git a/services/horizon/internal/actions/submit_transaction_async.go b/services/horizon/internal/actions/submit_transaction_async.go index ff1d8db4a9..0cce31b5f6 100644 --- a/services/horizon/internal/actions/submit_transaction_async.go +++ b/services/horizon/internal/actions/submit_transaction_async.go @@ -40,7 +40,7 @@ func (handler AsyncSubmitTransactionHandler) GetResource(_ HeaderWriter, r *http return nil, &problem.P{ Type: "transaction_submission_disabled", Title: "Transaction Submission Disabled", - Status: http.StatusMethodNotAllowed, + Status: http.StatusForbidden, Detail: "Transaction submission has been disabled for Horizon. " + "To enable it again, remove env variable DISABLE_TX_SUB.", Extras: map[string]interface{}{ diff --git a/services/horizon/internal/actions/submit_transaction_test.go b/services/horizon/internal/actions/submit_transaction_test.go index a15ce3bd94..eb1987bdea 100644 --- a/services/horizon/internal/actions/submit_transaction_test.go +++ b/services/horizon/internal/actions/submit_transaction_test.go @@ -178,7 +178,7 @@ func TestDisableTxSubFlagSubmission(t *testing.T) { var p = &problem.P{ Type: "transaction_submission_disabled", Title: "Transaction Submission Disabled", - Status: http.StatusMethodNotAllowed, + Status: http.StatusForbidden, Detail: "Transaction submission has been disabled for Horizon. " + "To enable it again, remove env variable DISABLE_TX_SUB.", Extras: map[string]interface{}{}, diff --git a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml index c4c86d29a7..f889cf4ec8 100644 --- a/services/horizon/internal/httpx/static/txsub_async_oapi.yaml +++ b/services/horizon/internal/httpx/static/txsub_async_oapi.yaml @@ -67,7 +67,7 @@ paths: value: type: "transaction_submission_disabled" title: "Transaction Submission Disabled" - status: 405 + status: 403 detail: "Transaction submission has been disabled for Horizon. To enable it again, remove env variable DISABLE_TX_SUB." extras: envelope_xdr: "" From 1932250b20cc8b7a2fa2c82253d9e990e0b79d8a Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 13:34:16 -0400 Subject: [PATCH 61/68] Fix failing unittest --- .../horizon/internal/actions/submit_transaction_async_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/horizon/internal/actions/submit_transaction_async_test.go b/services/horizon/internal/actions/submit_transaction_async_test.go index a741705a7f..258012bc3c 100644 --- a/services/horizon/internal/actions/submit_transaction_async_test.go +++ b/services/horizon/internal/actions/submit_transaction_async_test.go @@ -62,7 +62,7 @@ func TestAsyncSubmitTransactionHandler_DisabledTxSub(t *testing.T) { assert.IsType(t, &problem.P{}, err) p := err.(*problem.P) assert.Equal(t, "transaction_submission_disabled", p.Type) - assert.Equal(t, http.StatusMethodNotAllowed, p.Status) + assert.Equal(t, http.StatusForbidden, p.Status) } func TestAsyncSubmitTransactionHandler_MalformedTransaction(t *testing.T) { From e4f33e4c2edb94de704651e25d1f6a60f60da0bc Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 14:09:54 -0400 Subject: [PATCH 62/68] Revert submitter changes --- .../horizon/internal/txsub/helpers_test.go | 8 +++---- services/horizon/internal/txsub/main.go | 5 ++-- services/horizon/internal/txsub/submitter.go | 4 +--- .../horizon/internal/txsub/submitter_test.go | 24 +++++++++---------- services/horizon/internal/txsub/system.go | 10 ++++---- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/services/horizon/internal/txsub/helpers_test.go b/services/horizon/internal/txsub/helpers_test.go index ecc26d6ec8..af8928a0b4 100644 --- a/services/horizon/internal/txsub/helpers_test.go +++ b/services/horizon/internal/txsub/helpers_test.go @@ -9,12 +9,12 @@ package txsub import ( "context" "database/sql" + "github.com/prometheus/client_golang/prometheus" - "github.com/stellar/go/services/horizon/internal/ledger" - "github.com/stellar/go/xdr" + "github.com/stretchr/testify/mock" "github.com/stellar/go/services/horizon/internal/db2/history" - "github.com/stretchr/testify/mock" + "github.com/stellar/go/services/horizon/internal/ledger" ) // MockSubmitter is a test helper that simplements the Submitter interface @@ -24,7 +24,7 @@ type MockSubmitter struct { } // Submit implements `txsub.Submitter` -func (sub *MockSubmitter) Submit(ctx context.Context, env string, envelope xdr.TransactionEnvelope) SubmissionResult { +func (sub *MockSubmitter) Submit(ctx context.Context, env string) SubmissionResult { sub.WasSubmittedTo = true return sub.R } diff --git a/services/horizon/internal/txsub/main.go b/services/horizon/internal/txsub/main.go index d998d150a2..12432fa370 100644 --- a/services/horizon/internal/txsub/main.go +++ b/services/horizon/internal/txsub/main.go @@ -1,9 +1,8 @@ package txsub import ( - "time" - "context" + "time" "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stellar/go/xdr" @@ -42,7 +41,7 @@ type OpenSubmissionList interface { // provider. type Submitter interface { // Submit sends the provided transaction envelope to stellar-core - Submit(context.Context, string, xdr.TransactionEnvelope) SubmissionResult + Submit(context.Context, string) SubmissionResult } // Result represents the response from a ResultProvider. Given no diff --git a/services/horizon/internal/txsub/submitter.go b/services/horizon/internal/txsub/submitter.go index af67f132a3..694cc3b372 100644 --- a/services/horizon/internal/txsub/submitter.go +++ b/services/horizon/internal/txsub/submitter.go @@ -7,8 +7,6 @@ import ( "github.com/prometheus/client_golang/prometheus" - "github.com/stellar/go/xdr" - "github.com/stellar/go/clients/stellarcore" proto "github.com/stellar/go/protocols/stellarcore" "github.com/stellar/go/support/errors" @@ -38,7 +36,7 @@ type submitter struct { // Submit sends the provided envelope to stellar-core and parses the response into // a SubmissionResult -func (sub *submitter) Submit(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (result SubmissionResult) { +func (sub *submitter) Submit(ctx context.Context, rawTx string) (result SubmissionResult) { start := time.Now() defer func() { result.Duration = time.Since(start) diff --git a/services/horizon/internal/txsub/submitter_test.go b/services/horizon/internal/txsub/submitter_test.go index 1f00bc21a1..c98f0acb18 100644 --- a/services/horizon/internal/txsub/submitter_test.go +++ b/services/horizon/internal/txsub/submitter_test.go @@ -1,12 +1,12 @@ package txsub import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/stellar/go/xdr" - "github.com/stretchr/testify/assert" "net/http" "testing" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stellar/go/services/horizon/internal/test" ) @@ -20,7 +20,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr := s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr := s.Submit(ctx, "hello") assert.Nil(t, sr.Err) assert.True(t, sr.Duration > 0) assert.Equal(t, "hello", server.LastRequest.URL.Query().Get("blob")) @@ -33,24 +33,24 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.Nil(t, sr.Err) // Errors when the stellar-core url is empty s = NewDefaultSubmitter(http.DefaultClient, "", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) //errors when the stellar-core url is not parseable s = NewDefaultSubmitter(http.DefaultClient, "http://Not a url", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) // errors when the stellar-core url is not reachable s = NewDefaultSubmitter(http.DefaultClient, "http://127.0.0.1:65535", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) // errors when the stellar-core returns an unparseable response @@ -58,7 +58,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) // errors when the stellar-core returns an exception response @@ -66,7 +66,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "Invalid XDR") @@ -75,7 +75,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "NOTREAL") @@ -84,7 +84,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello", xdr.TransactionEnvelope{}) + sr = s.Submit(ctx, "hello") assert.IsType(t, &FailedTransactionError{}, sr.Err) ferr := sr.Err.(*FailedTransactionError) assert.Equal(t, "1234", ferr.ResultXDR) diff --git a/services/horizon/internal/txsub/system.go b/services/horizon/internal/txsub/system.go index 57846eb091..2232e61c8d 100644 --- a/services/horizon/internal/txsub/system.go +++ b/services/horizon/internal/txsub/system.go @@ -4,11 +4,13 @@ import ( "context" "database/sql" "fmt" - "github.com/stellar/go/services/horizon/internal/ledger" "sync" "time" + "github.com/stellar/go/services/horizon/internal/ledger" + "github.com/prometheus/client_golang/prometheus" + "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stellar/go/support/log" "github.com/stellar/go/xdr" @@ -109,7 +111,7 @@ func (sys *System) Submit( return } - sr := sys.submitOnce(ctx, rawTx, envelope) + sr := sys.submitOnce(ctx, rawTx) if sr.Err != nil { // any error other than "txBAD_SEQ" is a failure @@ -201,9 +203,9 @@ func (sys *System) deriveTxSubError(ctx context.Context) error { // Submit submits the provided base64 encoded transaction envelope to the // network using this submission system. -func (sys *System) submitOnce(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) SubmissionResult { +func (sys *System) submitOnce(ctx context.Context, rawTx string) SubmissionResult { // submit to stellar-core - sr := sys.Submitter.Submit(ctx, rawTx, envelope) + sr := sys.Submitter.Submit(ctx, rawTx) if sr.Err == nil { sys.Metrics.SuccessfulSubmissionsCounter.Inc() From 28e392ab508c4254d41a8d4e664b66a3913ed615 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 14:17:22 -0400 Subject: [PATCH 63/68] Fix failing submitter_test --- .../horizon/internal/txsub/submitter_test.go | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/services/horizon/internal/txsub/submitter_test.go b/services/horizon/internal/txsub/submitter_test.go index c98f0acb18..5662930b5d 100644 --- a/services/horizon/internal/txsub/submitter_test.go +++ b/services/horizon/internal/txsub/submitter_test.go @@ -10,6 +10,10 @@ import ( "github.com/stellar/go/services/horizon/internal/test" ) +const ( + TxXDR = "AAAAAAGUcmKO5465JxTSLQOQljwk2SfqAJmZSG6JH6wtqpwhAAABLAAAAAAAAAABAAAAAAAAAAEAAAALaGVsbG8gd29ybGQAAAAAAwAAAAAAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAAAvrwgAAAAAAAAAAAQAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNgAAAAAN4Lazj4x61AAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLaqcIQAAAEBKwqWy3TaOxoGnfm9eUjfTRBvPf34dvDA0Nf+B8z4zBob90UXtuCqmQqwMCyH+okOI3c05br3khkH0yP4kCwcE" +) + func TestDefaultSubmitter(t *testing.T) { ctx := test.Context() // submits to the configured stellar-core instance correctly @@ -20,10 +24,10 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s := NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr := s.Submit(ctx, "hello") + sr := s.Submit(ctx, TxXDR) assert.Nil(t, sr.Err) assert.True(t, sr.Duration > 0) - assert.Equal(t, "hello", server.LastRequest.URL.Query().Get("blob")) + assert.Equal(t, TxXDR, server.LastRequest.URL.Query().Get("blob")) // Succeeds when stellar-core gives the DUPLICATE response. server = test.NewStaticMockServer(`{ @@ -33,24 +37,24 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.Nil(t, sr.Err) // Errors when the stellar-core url is empty s = NewDefaultSubmitter(http.DefaultClient, "", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) //errors when the stellar-core url is not parseable s = NewDefaultSubmitter(http.DefaultClient, "http://Not a url", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) // errors when the stellar-core url is not reachable s = NewDefaultSubmitter(http.DefaultClient, "http://127.0.0.1:65535", prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) // errors when the stellar-core returns an unparseable response @@ -58,7 +62,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) // errors when the stellar-core returns an exception response @@ -66,7 +70,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "Invalid XDR") @@ -75,7 +79,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.NotNil(t, sr.Err) assert.Contains(t, sr.Err.Error(), "NOTREAL") @@ -84,7 +88,7 @@ func TestDefaultSubmitter(t *testing.T) { defer server.Close() s = NewDefaultSubmitter(http.DefaultClient, server.URL, prometheus.NewRegistry()) - sr = s.Submit(ctx, "hello") + sr = s.Submit(ctx, TxXDR) assert.IsType(t, &FailedTransactionError{}, sr.Err) ferr := sr.Err.(*FailedTransactionError) assert.Equal(t, "1234", ferr.ResultXDR) From ce3e6aaa19886524927635f745be786b3261d874 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 15:44:09 -0400 Subject: [PATCH 64/68] Revert import changes --- services/horizon/internal/txsub/helpers_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/horizon/internal/txsub/helpers_test.go b/services/horizon/internal/txsub/helpers_test.go index af8928a0b4..ad77f9a75d 100644 --- a/services/horizon/internal/txsub/helpers_test.go +++ b/services/horizon/internal/txsub/helpers_test.go @@ -11,10 +11,12 @@ import ( "database/sql" "github.com/prometheus/client_golang/prometheus" + + "github.com/stellar/go/services/horizon/internal/ledger" + "github.com/stretchr/testify/mock" "github.com/stellar/go/services/horizon/internal/db2/history" - "github.com/stellar/go/services/horizon/internal/ledger" ) // MockSubmitter is a test helper that simplements the Submitter interface From 30b0c2577bb4da9832b84759e9379fef39d71657 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 15:45:09 -0400 Subject: [PATCH 65/68] Revert import changes - 2 --- services/horizon/internal/txsub/helpers_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/horizon/internal/txsub/helpers_test.go b/services/horizon/internal/txsub/helpers_test.go index ad77f9a75d..aec9e68daf 100644 --- a/services/horizon/internal/txsub/helpers_test.go +++ b/services/horizon/internal/txsub/helpers_test.go @@ -11,12 +11,10 @@ import ( "database/sql" "github.com/prometheus/client_golang/prometheus" - "github.com/stellar/go/services/horizon/internal/ledger" - "github.com/stretchr/testify/mock" - "github.com/stellar/go/services/horizon/internal/db2/history" + "github.com/stretchr/testify/mock" ) // MockSubmitter is a test helper that simplements the Submitter interface From 1057ab39f6a08a519257fde37bb34bd72dda4d87 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 15:46:14 -0400 Subject: [PATCH 66/68] Revert import changes - 3 --- services/horizon/internal/txsub/helpers_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/horizon/internal/txsub/helpers_test.go b/services/horizon/internal/txsub/helpers_test.go index aec9e68daf..3c4cb6cb0b 100644 --- a/services/horizon/internal/txsub/helpers_test.go +++ b/services/horizon/internal/txsub/helpers_test.go @@ -9,7 +9,6 @@ package txsub import ( "context" "database/sql" - "github.com/prometheus/client_golang/prometheus" "github.com/stellar/go/services/horizon/internal/ledger" From 401d2e2ffae555dfc3e82f160defa972b82bbfd3 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 16:32:37 -0400 Subject: [PATCH 67/68] Remove integration test function --- services/horizon/internal/test/integration/integration.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/services/horizon/internal/test/integration/integration.go b/services/horizon/internal/test/integration/integration.go index eccc3616d3..d755d00252 100644 --- a/services/horizon/internal/test/integration/integration.go +++ b/services/horizon/internal/test/integration/integration.go @@ -1154,13 +1154,7 @@ func (i *Test) SubmitMultiSigTransaction( func (i *Test) AsyncSubmitTransaction( signer *keypair.Full, txParams txnbuild.TransactionParams, ) (proto.AsyncTransactionSubmissionResponse, error) { - return i.AsyncSubmitMultiSigTransaction([]*keypair.Full{signer}, txParams) -} - -func (i *Test) AsyncSubmitMultiSigTransaction( - signers []*keypair.Full, txParams txnbuild.TransactionParams, -) (proto.AsyncTransactionSubmissionResponse, error) { - tx, err := i.CreateSignedTransaction(signers, txParams) + tx, err := i.CreateSignedTransaction([]*keypair.Full{signer}, txParams) if err != nil { return proto.AsyncTransactionSubmissionResponse{}, err } From c8d4bad127041eb20508b871a336b78d57e7d119 Mon Sep 17 00:00:00 2001 From: Aditya Vyas Date: Thu, 25 Apr 2024 17:05:40 -0400 Subject: [PATCH 68/68] Update main.go --- services/horizon/internal/txsub/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/horizon/internal/txsub/main.go b/services/horizon/internal/txsub/main.go index 12432fa370..b466fc0055 100644 --- a/services/horizon/internal/txsub/main.go +++ b/services/horizon/internal/txsub/main.go @@ -1,9 +1,10 @@ package txsub import ( - "context" "time" + "context" + "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stellar/go/xdr" )