Skip to content

Commit

Permalink
Send an error response for Horizon.Problem
Browse files Browse the repository at this point in the history
  • Loading branch information
aditya1702 committed Sep 3, 2024
1 parent 676ad39 commit 893c278
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 4 deletions.
50 changes: 46 additions & 4 deletions clients/horizonclient/internal.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package horizonclient

import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/stellar/go/protocols/horizon"
"github.com/stellar/go/support/clock"
"github.com/stellar/go/support/errors"
)
Expand All @@ -27,10 +30,11 @@ func decodeResponse(resp *http.Response, object interface{}, horizonUrl string,
}
setCurrentServerTime(u.Hostname(), resp.Header["Date"], clock)

// 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") {
if isStatusCodeAnError(resp.StatusCode) {
if isAsyncTxSubRequest(resp) {
return decodeAsyncTxSubResponse(resp, object)
}

horizonError := &Error{
Response: resp,
}
Expand All @@ -47,6 +51,44 @@ func decodeResponse(resp *http.Response, object interface{}, horizonUrl string,
return
}

func isStatusCodeAnError(statusCode int) bool {
return !(statusCode >= 200 && statusCode < 300)
}

func isAsyncTxSubRequest(resp *http.Response) bool {
return resp.Request != nil && resp.Request.URL != nil && resp.Request.URL.Path == "/transactions_async"
}

func decodeAsyncTxSubResponse(resp *http.Response, object interface{}) error {
// We need to read the entire body in order to create 2 decoders later.
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "error reading response body")
}

// The first decoder converts the response to AsyncTransactionSubmissionResponse and checks
// the hash of the transaction. If the response was not a valid one, the hash will be empty.
decoder1 := json.NewDecoder(bytes.NewReader(bodyBytes))
err = decoder1.Decode(&object)
if err != nil {
return errors.Wrap(err, "error decoding response")
}
if asyncResp, ok := object.(*horizon.AsyncTransactionSubmissionResponse); ok && asyncResp.Hash != "" {
return nil
}

// Create a new reader for the second decoding. The second decoder decodes to Horizon.Problem object.
decoder2 := json.NewDecoder(bytes.NewReader(bodyBytes))
horizonErr := Error{
Response: resp,
}
err = decoder2.Decode(&horizonErr.Problem)
if err != nil {
return errors.Wrap(err, "error decoding horizon error")
}
return horizonErr
}

// countParams counts the number of parameters provided
func countParams(params ...interface{}) int {
counter := 0
Expand Down
26 changes: 26 additions & 0 deletions services/horizon/internal/integration/txsub_async_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/protocols/horizon"
Expand Down Expand Up @@ -131,6 +132,31 @@ func TestAsyncTxSub_SubmissionTryAgainLater(t *testing.T) {
})
}

func TestAsyncTxSub_TransactionMalformed(t *testing.T) {
itest := integration.NewTest(t, integration.Config{
EnableSorobanRPC: true,
HorizonEnvironment: map[string]string{
"MAX_HTTP_REQUEST_SIZE": "1800",
},
})
master := itest.Master()

// establish which account will be contract owner, and load it's current seq
sourceAccount, err := itest.Client().AccountDetail(horizonclient.AccountRequest{
AccountID: itest.Master().Address(),
})
require.NoError(t, err)

installContractOp := assembleInstallContractCodeOp(t, master.Address(), "soroban_sac_test.wasm")
preFlightOp, minFee := itest.PreflightHostFunctions(&sourceAccount, *installContractOp)
txParams := integration.GetBaseTransactionParamsWithFee(&sourceAccount, minFee+txnbuild.MinBaseFee, &preFlightOp)
_, err = itest.AsyncSubmitTransaction(master, txParams)
assert.EqualError(
t, err,
"horizon error: \"Transaction Malformed\" - check horizon.Error.Problem for more information",
)
}

func TestAsyncTxSub_GetOpenAPISpecResponse(t *testing.T) {
itest := integration.NewTest(t, integration.Config{})
res, err := http.Get(itest.AsyncTxSubOpenAPISpecURL())
Expand Down

0 comments on commit 893c278

Please sign in to comment.