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 58 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/services/horizon/captive-core
/services/horizon/horizon
/services/horizon/stellar-horizon
/bucket-cache
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
.vscode
.idea
debug
Expand Down
112 changes: 95 additions & 17 deletions clients/horizonclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}

Expand All @@ -505,20 +535,68 @@ 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 := c.validateTx(transaction, opts)
if err != nil {
return
}

txeBase64, err := transaction.Base64()
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.
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 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.
func (c *Client) AsyncSubmitFeeBumpTransaction(transaction *txnbuild.FeeBumpTransaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) {
return c.AsyncSubmitFeeBumpTransactionWithOptions(transaction, SubmitTxOpts{})
}

// 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.
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
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
// doesn't have a memo.
txeBase64, err := c.validateFeeBumpTx(transaction, opts)
if err != nil {
err = errors.Wrap(err, "Unable to convert transaction object to base64 string")
return
}

return c.SubmitTransactionXDR(txeBase64)
return c.AsyncSubmitTransactionXDR(txeBase64)
}

// 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.
func (c *Client) AsyncSubmitTransaction(transaction *txnbuild.Transaction) (txResp hProtocol.AsyncTransactionSubmissionResponse, err error) {
return c.AsyncSubmitTransactionWithOptions(transaction, SubmitTxOpts{})
}

// 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.
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.
txeBase64, err := c.validateTx(transaction, opts)
if err != nil {
return
}

return c.AsyncSubmitTransactionXDR(txeBase64)
}

// Transactions returns stellar transactions (https://developers.stellar.org/api/resources/transactions/list/)
Expand Down
5 changes: 4 additions & 1 deletion clients/horizonclient/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == nil || resp.Request.URL == nil || resp.Request.URL.Path != "/transactions_async") {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

@overcat overcat Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a problem with the implementation here. For AsyncSubmitTransactionXDR, if the user passes in an invalid XDR, the data returned by Horizon is as follows:

{
  "type": "https://stellar.org/horizon-errors/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": "AAAAAgAAAAAoXi7Kz8/PAl9GZl5AD09H080g961ptb1MMfj6evUsJwAAAGQACuJqAAAAEQAAAAEAAAAAAAAAAAAAAABmoxT9AAAAAQAAAA9IZWxsbywgU3RlbGxhciEAAAAAAQAAAAAAAAABAAAAAPSbkKYju7E2GtKbpAnQ0twJCAGBwIcxea690WueeVSjAAAAAAAAAADQsJmHAAAAAAAAAAF69SwnAAAAQHgHTIiwgcIvdm0//yTH9o9rsGlMCng7YjKrOszNaJ0qwvPeLs8/bNdTRdg51gs3/RQAhT4lDeJR4Bs5wgZjeMgM=",
    "error": {}
  }
}

However, due to this change, the err returned by AsyncSubmitTransactionXDR will be nil, which does not meet expectations.

BTW, for non-2xx status codes, Horizon returns responses in a format similar to the one above. The transactions_async endpoint is an exception to this.

horizonError := &Error{
Response: resp,
}
Expand Down
5 changes: 5 additions & 0 deletions clients/horizonclient/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,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)
Expand Down
30 changes: 30 additions & 0 deletions clients/horizonclient/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,36 @@
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)

Check failure on line 126 in clients/horizonclient/mocks.go

View workflow job for this annotation

GitHub Actions / golangci

m.Called undefined (type *MockClient has no field or method Called) (typecheck)
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)

Check failure on line 132 in clients/horizonclient/mocks.go

View workflow job for this annotation

GitHub Actions / golangci

m.Called undefined (type *MockClient has no field or method Called) (typecheck)
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)

Check failure on line 138 in clients/horizonclient/mocks.go

View workflow job for this annotation

GitHub Actions / golangci

m.Called undefined (type *MockClient has no field or method Called) (typecheck)
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)
Expand Down
78 changes: 78 additions & 0 deletions clients/stellarcore/metrics_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package stellarcore

import (
"context"
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
"time"

"github.com/prometheus/client_golang/prometheus"

proto "github.com/stellar/go/protocols/stellarcore"
"github.com/stellar/go/xdr"
)

type ClientWithMetrics interface {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error)
}

type clientWithMetrics struct {
CoreClient Client

TxSubMetrics struct {
// SubmissionDuration exposes timing metrics about the rate and latency of
// submissions to stellar-core
SubmissionDuration *prometheus.SummaryVec
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
}
}

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)

return response, err
}

func (c *clientWithMetrics) updateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) {
label := prometheus.Labels{}
if err != nil {
label["status"] = "request_error"
} else if response.IsException() {
label["status"] = "exception"
} else {
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"
}

c.TxSubMetrics.SubmissionDuration.With(label).Observe(duration)
}

func NewClientWithMetrics(client Client, registry *prometheus.Registry, prometheusSubsystem string) ClientWithMetrics {
submissionDuration := prometheus.NewSummaryVec(prometheus.SummaryOpts{
Namespace: "horizon",
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", "envelope_type"})

registry.MustRegister(
submissionDuration,
)

return &clientWithMetrics{
CoreClient: client,
TxSubMetrics: struct {
SubmissionDuration *prometheus.SummaryVec
}{
SubmissionDuration: submissionDuration,
},
}
}
22 changes: 22 additions & 0 deletions clients/stellarcore/mocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package stellarcore

import (
"context"
proto "github.com/stellar/go/protocols/stellarcore"
"github.com/stellar/go/xdr"
"github.com/stretchr/testify/mock"
)

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

Check failure on line 16 in clients/stellarcore/mocks.go

View workflow job for this annotation

GitHub Actions / golangci

m.Called undefined (type *MockClientWithMetrics has no field or method Called) (typecheck)
return args.Get(0).(*proto.TXResponse), args.Error(1)
}

func (m *MockClientWithMetrics) UpdateTxSubMetrics(duration float64, envelope xdr.TransactionEnvelope, response *proto.TXResponse, err error) {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
m.Called(duration, envelope, response, err)

Check failure on line 21 in clients/stellarcore/mocks.go

View workflow job for this annotation

GitHub Actions / golangci

m.Called undefined (type *MockClientWithMetrics has no field or method Called) (typecheck)
}
16 changes: 16 additions & 0 deletions protocols/horizon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,22 @@ 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"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps using error_result_xdr would be more consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@overcat The error_result_xdr is returned from core when the transaction submission has issues. In the invalid XDR case, it never reaches core but fails as a validation check from Horizon.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aditya1702 I think he's pointing out the naming convention errorResultXdr should probably be error_result_xdr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah my bad, misunderstood your comment. @overcat Yes that is a good catch and I will include this change for the next release. However, I do want to note that this will be a breaking change to the API

Copy link
Contributor

@overcat overcat Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aditya1702,

Yes, this will be a breaking update. Some measures need to be considered before making the changes in order to ensure a smooth transition downstream.

Did you check my other comment? That's a bug. I think we need to fix it. Below is the code to reproduce it.

package main

import client "github.com/stellar/go/clients/horizonclient"

func main() {
	client := client.Client{
		HorizonURL: "https://horizon-testnet.stellar.org",
	}
	_, err := client.AsyncSubmitTransactionXDR("invalidAAAAAgAAAAoXi7Kz8/PAl9GZl5AD09H080g961tb1MMfj6evUsJwAAAGQACuJqAAAAEgAAAAEAAAAAAAAAAAAAAABmo4DLAAAAAQAAAA9IZWxsbywgU3RlbGxhciEAAAAAAQAAAAAAAAABAAAAAPSbkKYju7E2GtKbpAnQ0twJCAGBwIcxea690WueeVSjAAAAAAAAAADQsJmHAAAAAAAAAAF69SwnAAAAQFAaIAq+4YAPnY7BEukwtmtiXVvHvjPi+6kJUpl/ljoYSmnZcMIUSFHH7LGct0ftWl7PnUKGE4JGDFk38tg8YAs=")
	if err != nil {
		println("error is not empty")
		panic(err)
	}
	println("We shouldn't have arrived here.")
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@overcat Ah gotcha. Yes this is a problem here and I will need to change the condition in the internal.go file you linked above. Thanks for spotting this!

// 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"`
// 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".
Expand Down
4 changes: 2 additions & 2 deletions services/friendbot/internal/friendbot_handler.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions services/horizon/internal/actions/claimable_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
4 changes: 4 additions & 0 deletions services/horizon/internal/actions/fee_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
4 changes: 4 additions & 0 deletions services/horizon/internal/actions/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading