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 32 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
72 changes: 72 additions & 0 deletions clients/horizonclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,78 @@ 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.
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.
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 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.
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) {
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
10 changes: 10 additions & 0 deletions clients/stellarcore/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ 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)
}

// 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
129 changes: 129 additions & 0 deletions clients/stellarcore/metrics_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package stellarcore

import (
"context"
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
"github.com/prometheus/client_golang/prometheus"
proto "github.com/stellar/go/protocols/stellarcore"
"github.com/stellar/go/xdr"
"time"
)

type ClientWithMetricsInterface interface {
SubmitTransaction(ctx context.Context, rawTx string, envelope xdr.TransactionEnvelope) (resp *proto.TXResponse, err error)
}

type ClientWithMetrics struct {
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
CoreClient ClientInterface

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

// 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

// 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
}
}

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) {
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.TxSubMetrics.SubmissionDuration.With(label).Observe(duration)
c.TxSubMetrics.SubmissionsCounter.With(label).Inc()

switch envelope.Type {
case xdr.EnvelopeTypeEnvelopeTypeTxV0:
c.TxSubMetrics.V0TransactionsCounter.With(label).Inc()
case xdr.EnvelopeTypeEnvelopeTypeTx:
c.TxSubMetrics.V1TransactionsCounter.With(label).Inc()
case xdr.EnvelopeTypeEnvelopeTypeTxFeeBump:
c.TxSubMetrics.FeeBumpTransactionsCounter.With(label).Inc()
}
}

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"})
submissionsCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "horizon",
Subsystem: prometheusSubsystem,
Name: "submissions_count",
Help: "number of submissions, sliding window = 10m",
}, []string{"status"})
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
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"})
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved

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,
},
}
}
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)
}
21 changes: 21 additions & 0 deletions protocols/horizon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
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!

// 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"`
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
// 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"`
aditya1702 marked this conversation as resolved.
Show resolved Hide resolved
// 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
12 changes: 6 additions & 6 deletions services/horizon/internal/actions/submit_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ 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/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 @@ -36,7 +36,7 @@ type envelopeInfo struct {
parsed xdr.TransactionEnvelope
}

func (handler SubmitTransactionHandler) extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) {
func extractEnvelopeInfo(raw string, passphrase string) (envelopeInfo, error) {
result := envelopeInfo{raw: raw}
err := xdr.SafeUnmarshalBase64(raw, &result.parsed)
if err != nil {
Expand All @@ -59,7 +59,7 @@ func (handler SubmitTransactionHandler) extractEnvelopeInfo(raw string, passphra
return result, nil
}

func (handler SubmitTransactionHandler) validateBodyType(r *http.Request) error {
func validateBodyType(r *http.Request) error {
c := r.Header.Get("Content-Type")
if c == "" {
return nil
Expand Down Expand Up @@ -137,7 +137,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 +157,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
Loading
Loading