Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

services/horizon + horizonclient: Add new async transaction submission endpoint #5188

Merged
merged 86 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
475af3f
Add new txsub endpoint - 1
aditya1702 Jan 22, 2024
d4555b3
Add new txsub endpoint - 2
aditya1702 Jan 23, 2024
aa4084a
Merge branch 'master' into async-txsub
aditya1702 Jan 23, 2024
e883120
Add new txsub endpoint - 3
aditya1702 Jan 23, 2024
00e1a29
Update Status to TxStatus
aditya1702 Jan 29, 2024
28a009e
Add unittests for new endpoint
aditya1702 Jan 31, 2024
2ed0054
Create submit_transaction_async_test.go
aditya1702 Jan 31, 2024
6ff4fa2
Fix goimports
aditya1702 Jan 31, 2024
525fb0e
Rearrange code and remove duplicate code
aditya1702 Jan 31, 2024
9b7e3f6
Merge branch 'master' into async-txsub
aditya1702 Jan 31, 2024
31fd091
Merge branch 'master' into async-txsub
aditya1702 Feb 9, 2024
d425558
Add metrics - 1
aditya1702 Feb 12, 2024
4f30e29
Merge branch 'master' into async-txsub
aditya1702 Feb 20, 2024
326e21e
Add metrics - 2
aditya1702 Feb 20, 2024
37d01ab
Add metrics - 3
aditya1702 Feb 22, 2024
c7e46af
Fix failing unittest
aditya1702 Feb 23, 2024
ed812ea
Add new endpoint to go sdk + integration test
aditya1702 Feb 23, 2024
7450b0a
Merge branch 'master' into async-txsub
aditya1702 Feb 26, 2024
808a4d8
Small changes - 1
aditya1702 Feb 26, 2024
17f8fee
Merge branch 'master' into async-txsub
aditya1702 Feb 27, 2024
1725c91
Add openAPI taml
aditya1702 Feb 29, 2024
9dfc179
Address review changes - 1
aditya1702 Mar 4, 2024
9a4e0d8
Remove private methods from interface
aditya1702 Mar 4, 2024
2b97e2d
Use common metrics client for legacy and async txsub
aditya1702 Mar 4, 2024
1edb2b0
Fix submitter test
aditya1702 Mar 4, 2024
55fcb5f
Merge branch 'master' into async-txsub
aditya1702 Mar 4, 2024
fd03686
Update submit_transaction_async.go
aditya1702 Mar 4, 2024
b95d57c
Fix failing test
aditya1702 Mar 4, 2024
0e4142a
Update txsub_async_oapi.yaml
aditya1702 Mar 4, 2024
6ebb93a
Merge branch 'master' into async-txsub
aditya1702 Mar 5, 2024
b879bd4
Update submitter.go
aditya1702 Mar 5, 2024
d1a4eb9
Interface method change
aditya1702 Mar 5, 2024
aa31ab2
Merge branch 'master' into async-txsub
aditya1702 Mar 6, 2024
085352f
Remove duplicate code
aditya1702 Mar 6, 2024
66a99df
Merge branch 'master' into async-txsub
aditya1702 Mar 6, 2024
2ba210d
Add test for GET /transactions-async
aditya1702 Mar 6, 2024
d331e41
Encapsulation - 1
aditya1702 Mar 6, 2024
a096b5f
Change endpoint naming
aditya1702 Mar 7, 2024
63ce1b9
Pass interface instead of client
aditya1702 Mar 7, 2024
0f14793
Remove ClientInterface
aditya1702 Mar 7, 2024
17611aa
Merge branch 'master' into async-txsub
aditya1702 Mar 11, 2024
e98fd22
Merge branch 'master' into async-txsub
aditya1702 Mar 25, 2024
eeddadc
Merge branch 'master' into async-txsub
aditya1702 Mar 25, 2024
f5ddbf2
Remove HTTP Status from submission response
aditya1702 Mar 26, 2024
5e498fb
Add logging statements
aditya1702 Mar 26, 2024
ef96628
Merge branch 'master' into async-txsub
aditya1702 Mar 26, 2024
48932aa
Fix failing integration tests
aditya1702 Mar 27, 2024
9f50ddd
Fix failing tests - 1
aditya1702 Mar 27, 2024
f34abbc
Add back deleted files
aditya1702 Apr 2, 2024
8d527f2
Remove circular import
aditya1702 Apr 2, 2024
c44ca0d
Merge branch 'master' into async-txsub
aditya1702 Apr 2, 2024
9b3deb9
Group metrics into submission duration
aditya1702 Apr 2, 2024
ee31a95
Group metrics into submission duration - 2
aditya1702 Apr 2, 2024
c646e0b
Remove logging statements where not needed
aditya1702 Apr 2, 2024
42cd689
Change to internal server error
aditya1702 Apr 2, 2024
21687d2
Use request context logger
aditya1702 Apr 2, 2024
e931893
Use interface method for setting http status
aditya1702 Apr 2, 2024
2114193
Remove not needed metrics
aditya1702 Apr 3, 2024
3aca364
Remove version
aditya1702 Apr 10, 2024
5c0089e
add error in extras
aditya1702 Apr 19, 2024
d5b3459
Merge branch 'master' into async-txsub
aditya1702 Apr 19, 2024
9517e76
Resolve merge conflicts
aditya1702 Apr 19, 2024
122a087
Add TODO for problem response
aditya1702 Apr 19, 2024
c571dd5
Adding and removing logging statements
aditya1702 Apr 19, 2024
7e3e495
Move interface to async handler file
aditya1702 Apr 19, 2024
03b2c66
change httpstatus interface definition
aditya1702 Apr 19, 2024
c869205
Add deleted files back
aditya1702 Apr 19, 2024
71893ba
Revert friendbot change
aditya1702 Apr 22, 2024
be59a2c
Add test for getting pending tx
aditya1702 Apr 22, 2024
667e6b2
Merge branch 'master' into async-txsub
aditya1702 Apr 22, 2024
d632a67
Fix failing test
aditya1702 Apr 22, 2024
d31a01e
remove metrics struct and make vars private
aditya1702 Apr 25, 2024
62d02d1
pass only rawTx string
aditya1702 Apr 25, 2024
32c71f8
Move mock to test file
aditya1702 Apr 25, 2024
d3b8c1e
Make core client private
aditya1702 Apr 25, 2024
573bdb6
Remove UpdateTxSubMetrics func
aditya1702 Apr 25, 2024
00985db
Change http status for DISABLE_TX_SUB
aditya1702 Apr 25, 2024
6e253de
Merge branch 'master' into async-txsub
aditya1702 Apr 25, 2024
1932250
Fix failing unittest
aditya1702 Apr 25, 2024
e4f33e4
Revert submitter changes
aditya1702 Apr 25, 2024
28e392a
Fix failing submitter_test
aditya1702 Apr 25, 2024
ce3e6aa
Revert import changes
aditya1702 Apr 25, 2024
30b0c25
Revert import changes - 2
aditya1702 Apr 25, 2024
1057ab3
Revert import changes - 3
aditya1702 Apr 25, 2024
401d2e2
Remove integration test function
aditya1702 Apr 25, 2024
c8d4bad
Update main.go
aditya1702 Apr 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions clients/stellarcore/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ type Client struct {
URL string
}

type ClientInterface interface {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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)
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
Expand Down
71 changes: 71 additions & 0 deletions clients/stellarcore/mocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 {
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)
}
55 changes: 2 additions & 53 deletions services/horizon/internal/actions/submit_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -29,53 +25,6 @@ type SubmitTransactionHandler struct {
CoreStateGetter
}

type envelopeInfo struct {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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
}

Expand All @@ -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",
Expand Down
165 changes: 165 additions & 0 deletions services/horizon/internal/actions/submit_transaction_async.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package actions

import (
"net/http"

"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"
)

const (
HTTPStatusCodeForPending = http.StatusCreated
HTTPStatusCodeForDuplicate = http.StatusConflict
HTTPStatusCodeForTryAgainLater = http.StatusServiceUnavailable
HTTPStatusCodeForError = http.StatusBadRequest
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
)

type AsyncSubmitTransactionHandler struct {
NetworkPassphrase string
DisableTxSub bool
CoreClient stellarcore.ClientInterface
CoreStateGetter
}

// TransactionSubmissionResponse represents the response returned by Horizon
// when using the transaction-submission-v2 endpoint.
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.
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"`

Check failure on line 41 in services/horizon/internal/actions/submit_transaction_async.go

View workflow job for this annotation

GitHub Actions / golangci

ST1003: struct field HttpStatus should be HTTPStatus (stylecheck)
// 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) {

Check failure on line 47 in services/horizon/internal/actions/submit_transaction_async.go

View workflow job for this annotation

GitHub Actions / golangci

Function 'GetResource' is too long (117 > 100) (funlen)
if err := 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,
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
Detail: "Transaction submission has been disabled for Horizon. " +
"To enable it again, remove env variable DISABLE_TX_SUB.",
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
Extras: map[string]interface{}{
"envelope_xdr": raw,
},
}
}

info, err := extractEnvelopeInfo(raw, handler.NetworkPassphrase)
if err != nil {
return nil, &problem.P{
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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.CoreClient.SubmitTransaction(r.Context(), info.raw)
if err != nil {
return nil, &problem.P{
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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{
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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",

Check failure on line 117 in services/horizon/internal/actions/submit_transaction_async.go

View workflow job for this annotation

GitHub Actions / golangci

line is 142 characters (lll)
Extras: map[string]interface{}{
"envelope_xdr": raw,
"error": resp.Exception,
},
}
}

switch resp.Status {
case proto.TXStatusError:
return TransactionSubmissionResponse{
ErrorResultXDR: resp.Error,
DiagnosticEventsXDR: resp.DiagnosticEvents,
TxStatus: resp.Status,
HttpStatus: HTTPStatusCodeForError,
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 TransactionSubmissionResponse{
TxStatus: resp.Status,
HttpStatus: httpStatus,
Hash: info.hash,
}, nil
default:
return nil, &problem.P{
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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",

Check failure on line 157 in services/horizon/internal/actions/submit_transaction_async.go

View workflow job for this annotation

GitHub Actions / golangci

line is 147 characters (lll)
Extras: map[string]interface{}{
"envelope_xdr": raw,
"error": resp.Error,
},
}
}

}
Loading
Loading