Skip to content

Commit

Permalink
services/horizon/internal: Add flag to limit request body size (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
tamirms committed Sep 13, 2023
1 parent a2eca5f commit f85102d
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 2 deletions.
1 change: 1 addition & 0 deletions services/horizon/internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ func (a *App) init() error {
SSEUpdateFrequency: a.config.SSEUpdateFrequency,
StaleThreshold: a.config.StaleThreshold,
ConnectionTimeout: a.config.ConnectionTimeout,
MaxHTTPRequestSize: a.config.MaxHTTPRequestSize,
NetworkPassphrase: a.config.NetworkPassphrase,
MaxPathLength: a.config.MaxPathLength,
MaxAssetsPerPathRequest: a.config.MaxAssetsPerPathRequest,
Expand Down
2 changes: 2 additions & 0 deletions services/horizon/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type Config struct {

SSEUpdateFrequency time.Duration
ConnectionTimeout time.Duration
// MaxHTTPRequestSize is the maximum allowed request payload size
MaxHTTPRequestSize uint
RateQuota *throttled.RateQuota
FriendbotURL *url.URL
LogLevel logrus.Level
Expand Down
12 changes: 11 additions & 1 deletion services/horizon/internal/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/viper"

"github.com/stellar/throttled"

"github.com/stellar/go/ingest/ledgerbackend"
"github.com/stellar/go/network"
"github.com/stellar/go/services/horizon/internal/db2/schema"
Expand All @@ -20,7 +22,6 @@ import (
"github.com/stellar/go/support/db"
"github.com/stellar/go/support/errors"
"github.com/stellar/go/support/log"
"github.com/stellar/throttled"
)

const (
Expand Down Expand Up @@ -62,6 +63,8 @@ const (
StellarPubnet = "pubnet"
// StellarTestnet is a constant representing the Stellar test network
StellarTestnet = "testnet"

defaultMaxHTTPRequestSize = uint(200 * 1024)
)

// validateBothOrNeither ensures that both options are provided, if either is provided.
Expand Down Expand Up @@ -366,6 +369,13 @@ func Flags() (*Config, support.ConfigOptions) {
CustomSetValue: support.SetDuration,
Usage: "defines the timeout of connection after which 504 response will be sent or stream will be closed, if Horizon is behind a load balancer with idle connection timeout, this should be set to a few seconds less that idle timeout, does not apply to POST /transactions",
},
&support.ConfigOption{
Name: "max-http-request-size",
ConfigKey: &config.MaxHTTPRequestSize,
OptType: types.Uint,
FlagDefault: defaultMaxHTTPRequestSize,
Usage: "sets the limit on the maximum allowed http request payload size, default is 200kb, to disable the limit check, set to 0, only do so if you acknowledge the implications of accepting unbounded http request payload sizes.",

Check failure on line 377 in services/horizon/internal/flags.go

View workflow job for this annotation

GitHub Actions / golangci

line is 237 characters (lll)
},
&support.ConfigOption{
Name: "per-hour-rate-limit",
ConfigKey: &config.RateQuota,
Expand Down
6 changes: 6 additions & 0 deletions services/horizon/internal/httpx/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type RouterConfig struct {
SSEUpdateFrequency time.Duration
StaleThreshold uint
ConnectionTimeout time.Duration
MaxHTTPRequestSize uint
NetworkPassphrase string
MaxPathLength uint
MaxAssetsPerPathRequest int
Expand Down Expand Up @@ -89,6 +90,11 @@ func (r *Router) addMiddleware(config *RouterConfig,
}))
r.Use(loggerMiddleware(serverMetrics))
r.Use(timeoutMiddleware(config.ConnectionTimeout))
if config.MaxHTTPRequestSize > 0 {
r.Use(func(handler http.Handler) http.Handler {
return http.MaxBytesHandler(handler, int64(config.MaxHTTPRequestSize))
})
}
r.Use(recoverMiddleware)
r.Use(chimiddleware.Compress(flate.DefaultCompression, "application/hal+json"))

Expand Down
60 changes: 59 additions & 1 deletion services/horizon/internal/integration/txsub_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package integration

import (
"strings"
"testing"

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

"github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/services/horizon/internal/test/integration"
"github.com/stellar/go/txnbuild"
"github.com/stretchr/testify/assert"
"github.com/stellar/go/xdr"
)

func TestTxSub(t *testing.T) {
Expand Down Expand Up @@ -50,3 +55,56 @@ func TestTxSub(t *testing.T) {
assert.Error(t, err)
})
}

func TestTxSubLimitsBodySize(t *testing.T) {
if integration.GetCoreMaxSupportedProtocol() < 20 {
t.Skip("This test run does not support less than Protocol 20")
}

itest := integration.NewTest(t, integration.Config{
ProtocolVersion: 20,
EnableSorobanRPC: true,
HorizonEnvironment: map[string]string{
"MAX_HTTP_REQUEST_SIZE": "5000",
},
})

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

contract := []byte(strings.Repeat("a", 10*1024))
installContractOp := &txnbuild.InvokeHostFunction{
HostFunction: xdr.HostFunction{
Type: xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm,
Wasm: &contract,
},
SourceAccount: itest.Master().Address(),
}
preFlightOp, minFee := itest.PreflightHostFunctions(&sourceAccount, *installContractOp)
_, err = itest.SubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee, &preFlightOp)
assert.EqualError(
t, err,
"horizon error: \"Transaction Malformed\" - check horizon.Error.Problem for more information",
)

sourceAccount, err = itest.Client().AccountDetail(horizonclient.AccountRequest{
AccountID: itest.Master().Address(),
})
require.NoError(t, err)

contract = []byte(strings.Repeat("a", 2*1024))
installContractOp = &txnbuild.InvokeHostFunction{
HostFunction: xdr.HostFunction{
Type: xdr.HostFunctionTypeHostFunctionTypeUploadContractWasm,
Wasm: &contract,
},
SourceAccount: itest.Master().Address(),
}
preFlightOp, minFee = itest.PreflightHostFunctions(&sourceAccount, *installContractOp)
tx, err := itest.SubmitOperationsWithFee(&sourceAccount, itest.Master(), minFee, &preFlightOp)
require.NoError(t, err)
require.True(t, tx.Successful)
}

0 comments on commit f85102d

Please sign in to comment.