From 5cf7ddcf6bade41ba85e9c3dbf94bfcf82c9a2bb Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 5 Feb 2025 15:08:13 +0100 Subject: [PATCH] ingest: Allow enabling Captive Core's fast HTTP server (#5586) --- .github/actions/setup-go/action.yml | 4 +- .github/workflows/horizon.yml | 4 +- .../testdata/expected-query-params.cfg | 24 ++++++++ .../testdata/sample-appendix-query-params.cfg | 14 +++++ ingest/ledgerbackend/toml.go | 57 +++++++++++++++++-- ingest/ledgerbackend/toml_test.go | 55 ++++++++++++++++-- services/horizon/internal/flags_test.go | 4 +- 7 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 ingest/ledgerbackend/testdata/expected-query-params.cfg create mode 100644 ingest/ledgerbackend/testdata/sample-appendix-query-params.cfg diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 1bfd4a41f8..d63beaa8f3 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -32,7 +32,7 @@ runs: run: echo 'PREFIX=${{ github.workflow }}-${{ github.job }}-${{ runner.os }}-${{ inputs.go-version }}-matrix(${{ join(matrix.*,'|') }})' >> $GITHUB_ENV # Cache the Go Modules downloaded during the job. - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ env.PREFIX }}-go-mod-${{ hashFiles('**/go.sum') }} @@ -40,7 +40,7 @@ runs: # Cache any build and test artifacts during the job, which will speed up # rebuilds and cause test runs to skip tests that have no reason to rerun. - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: ~/.cache/go-build key: ${{ env.PREFIX }}-go-build-${{ github.ref }}-${{ hashFiles('**', '!.git') }} diff --git a/.github/workflows/horizon.yml b/.github/workflows/horizon.yml index 83bc1e6827..0ab7cc7807 100644 --- a/.github/workflows/horizon.yml +++ b/.github/workflows/horizon.yml @@ -103,7 +103,7 @@ jobs: - name: Restore Horizon binary and integration tests source hash to cache id: horizon_binary_tests_hash - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: ./empty lookup-only: true @@ -114,7 +114,7 @@ jobs: - name: Save Horizon binary and integration tests source hash to cache if: ${{ success() && steps.horizon_binary_tests_hash.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: ./empty key: ${{ env.COMBINED_SOURCE_HASH }} diff --git a/ingest/ledgerbackend/testdata/expected-query-params.cfg b/ingest/ledgerbackend/testdata/expected-query-params.cfg new file mode 100644 index 0000000000..86657a46a7 --- /dev/null +++ b/ingest/ledgerbackend/testdata/expected-query-params.cfg @@ -0,0 +1,24 @@ +# Generated file, do not edit +BUCKETLIST_DB_INDEX_PAGE_SIZE_EXPONENT = 12 +DATABASE = "sqlite3://stellar.db" +DEPRECATED_SQL_LEDGER_STATE = false +FAILURE_SAFETY = -1 +HTTP_PORT = 11626 +HTTP_QUERY_PORT = 11628 +LOG_FILE_PATH = "" +NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015" +QUERY_SNAPSHOT_LEDGERS = 2 +QUERY_THREAD_POOL_SIZE = 4 + +[[HOME_DOMAINS]] + HOME_DOMAIN = "testnet.stellar.org" + QUALITY = "MEDIUM" + +[[VALIDATORS]] + ADDRESS = "localhost:123" + HOME_DOMAIN = "testnet.stellar.org" + NAME = "sdf_testnet_1" + PUBLIC_KEY = "GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" + +[HISTORY.h0] + get = "curl -sf http://localhost:1170/{0} -o {1}" diff --git a/ingest/ledgerbackend/testdata/sample-appendix-query-params.cfg b/ingest/ledgerbackend/testdata/sample-appendix-query-params.cfg new file mode 100644 index 0000000000..4a784b563d --- /dev/null +++ b/ingest/ledgerbackend/testdata/sample-appendix-query-params.cfg @@ -0,0 +1,14 @@ +# parameters to enable Captive Core's high-performance HTTP server +HTTP_QUERY_PORT=11628 +QUERY_THREAD_POOL_SIZE=4 +QUERY_SNAPSHOT_LEDGERS=2 + +[[HOME_DOMAINS]] +HOME_DOMAIN="testnet.stellar.org" +QUALITY="MEDIUM" + +[[VALIDATORS]] +NAME="sdf_testnet_1" +HOME_DOMAIN="testnet.stellar.org" +PUBLIC_KEY="GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" +ADDRESS="localhost:123" diff --git a/ingest/ledgerbackend/toml.go b/ingest/ledgerbackend/toml.go index 28f4f8cce6..b2d4d239d4 100644 --- a/ingest/ledgerbackend/toml.go +++ b/ingest/ledgerbackend/toml.go @@ -106,6 +106,9 @@ type captiveCoreTomlValues struct { EnableDiagnosticsForTxSubmission *bool `toml:"ENABLE_DIAGNOSTICS_FOR_TX_SUBMISSION,omitempty"` EnableEmitSorobanTransactionMetaExtV1 *bool `toml:"EMIT_SOROBAN_TRANSACTION_META_EXT_V1,omitempty"` EnableEmitLedgerCloseMetaExtV1 *bool `toml:"EMIT_LEDGER_CLOSE_META_EXT_V1,omitempty"` + HTTPQueryPort *uint `toml:"HTTP_QUERY_PORT,omitempty"` + QueryThreadPoolSize *uint `toml:"QUERY_THREAD_POOL_SIZE,omitempty"` + QuerySnapshotLedgers *uint `toml:"QUERY_SNAPSHOT_LEDGERS,omitempty"` } // QuorumSetIsConfigured returns true if there is a quorum set defined in the configuration. @@ -321,6 +324,12 @@ func (c *CaptiveCoreToml) unmarshal(data []byte, strict bool) error { return nil } +type HTTPQueryServerParams struct { + Port uint16 + ThreadPoolSize uint16 + SnapshotLedgers uint16 +} + // CaptiveCoreTomlParams defines captive core configuration provided by Horizon flags. type CaptiveCoreTomlParams struct { // NetworkPassphrase is the Stellar network passphrase used by captive core when connecting to the Stellar network. @@ -346,6 +355,8 @@ type CaptiveCoreTomlParams struct { EnforceSorobanDiagnosticEvents bool // Enfore EnableSorobanTransactionMetaExtV1 when not disabled explicitly EnforceSorobanTransactionMetaExtV1 bool + // Fast HTTP Query Server parameters + HTTPQueryServerParams *HTTPQueryServerParams } // NewCaptiveCoreTomlFromFile constructs a new CaptiveCoreToml instance by merging configuration @@ -498,6 +509,18 @@ func (c *CaptiveCoreToml) setDefaults(params CaptiveCoreTomlParams) { if params.EnforceSorobanTransactionMetaExtV1 { enforceOption(&c.EnableEmitSorobanTransactionMetaExtV1) } + + if params.HTTPQueryServerParams != nil { + port := uint(params.HTTPQueryServerParams.Port) + c.HTTPQueryPort = &port + + ledgers := uint(params.HTTPQueryServerParams.SnapshotLedgers) + c.QuerySnapshotLedgers = &ledgers + + poolSize := uint(params.HTTPQueryServerParams.ThreadPoolSize) + c.QueryThreadPoolSize = &poolSize + + } } func enforceOption(opt **bool) { @@ -516,7 +539,7 @@ func enforceOption(opt **bool) { func (c *CaptiveCoreToml) validate(params CaptiveCoreTomlParams) error { if def := c.tree.Has("NETWORK_PASSPHRASE"); def && c.NetworkPassphrase != params.NetworkPassphrase { return fmt.Errorf( - "NETWORK_PASSPHRASE in captive core config file: %s does not match Horizon network-passphrase flag: %s", + "NETWORK_PASSPHRASE in captive core config file: %s does not match passed configuration (%s)", c.NetworkPassphrase, params.NetworkPassphrase, ) @@ -524,7 +547,7 @@ func (c *CaptiveCoreToml) validate(params CaptiveCoreTomlParams) error { if def := c.tree.Has("HTTP_PORT"); def && params.HTTPPort != nil && c.HTTPPort != *params.HTTPPort { return fmt.Errorf( - "HTTP_PORT in captive core config file: %d does not match Horizon captive-core-http-port flag: %d", + "HTTP_PORT in captive core config file: %d does not match passed configuration (%d)", c.HTTPPort, *params.HTTPPort, ) @@ -532,7 +555,7 @@ func (c *CaptiveCoreToml) validate(params CaptiveCoreTomlParams) error { if def := c.tree.Has("PEER_PORT"); def && params.PeerPort != nil && c.PeerPort != *params.PeerPort { return fmt.Errorf( - "PEER_PORT in captive core config file: %d does not match Horizon captive-core-peer-port flag: %d", + "PEER_PORT in captive core config file: %d does not match passed configuration (%d)", c.PeerPort, *params.PeerPort, ) @@ -540,12 +563,38 @@ func (c *CaptiveCoreToml) validate(params CaptiveCoreTomlParams) error { if def := c.tree.Has("LOG_FILE_PATH"); def && params.LogPath != nil && c.LogFilePath != *params.LogPath { return fmt.Errorf( - "LOG_FILE_PATH in captive core config file: %s does not match Horizon captive-core-log-path flag: %s", + "LOG_FILE_PATH in captive core config file: %s does not match passed configuration (%s)", c.LogFilePath, *params.LogPath, ) } + if params.HTTPQueryServerParams != nil { + if c.HTTPQueryPort != nil && *c.HTTPQueryPort != uint(params.HTTPQueryServerParams.Port) { + return fmt.Errorf( + "HTTP_QUERY_PORT in captive core config file: %d does not match passed configuration (%d)", + c.PeerPort, + *params.PeerPort, + ) + } + + if c.QueryThreadPoolSize != nil && *c.QueryThreadPoolSize != uint(params.HTTPQueryServerParams.ThreadPoolSize) { + return fmt.Errorf( + "QUERY_THREADPOOL_SIZE in captive core config file: %d does not match passed configuration (%d)", + c.PeerPort, + *params.PeerPort, + ) + } + + if c.QuerySnapshotLedgers != nil && *c.QuerySnapshotLedgers != uint(params.HTTPQueryServerParams.SnapshotLedgers) { + return fmt.Errorf( + "QUERY_SNAPSHOT_LEDGERS in captive core config file: %d does not match passed configuration (%d)", + c.PeerPort, + *params.PeerPort, + ) + } + } + if c.tree.Has("DEPRECATED_SQL_LEDGER_STATE") { if params.UseDB && *c.DeprecatedSqlLedgerState { return fmt.Errorf("CAPTIVE_CORE_USE_DB parameter is set to true, indicating stellar-core on-disk mode," + diff --git a/ingest/ledgerbackend/toml_test.go b/ingest/ledgerbackend/toml_test.go index 42412b85f1..9bee190d09 100644 --- a/ingest/ledgerbackend/toml_test.go +++ b/ingest/ledgerbackend/toml_test.go @@ -36,8 +36,7 @@ func TestCaptiveCoreTomlValidation(t *testing.T) { peerPort: newUint(12345), logPath: nil, expectedError: "invalid captive core toml: NETWORK_PASSPHRASE in captive core config file: " + - "Public Global Stellar Network ; September 2015 does not match Horizon network-passphrase " + - "flag: bogus passphrase", + "Public Global Stellar Network ; September 2015 does not match passed configuration (bogus passphrase)", }, { name: "mismatching HTTP_PORT", @@ -47,7 +46,7 @@ func TestCaptiveCoreTomlValidation(t *testing.T) { peerPort: newUint(12345), logPath: nil, expectedError: "invalid captive core toml: HTTP_PORT in captive core config file: 6789 " + - "does not match Horizon captive-core-http-port flag: 1161", + "does not match passed configuration (1161)", }, { name: "mismatching PEER_PORT", @@ -57,7 +56,7 @@ func TestCaptiveCoreTomlValidation(t *testing.T) { peerPort: newUint(2346), logPath: nil, expectedError: "invalid captive core toml: PEER_PORT in captive core config file: 12345 " + - "does not match Horizon captive-core-peer-port flag: 2346", + "does not match passed configuration (2346)", }, { name: "mismatching LOG_FILE_PATH", @@ -67,7 +66,7 @@ func TestCaptiveCoreTomlValidation(t *testing.T) { peerPort: newUint(12345), logPath: newString("/my/test/path"), expectedError: "invalid captive core toml: LOG_FILE_PATH in captive core config file: " + - "does not match Horizon captive-core-log-path flag: /my/test/path", + "does not match passed configuration (/my/test/path)", }, { name: "duplicate HOME_DOMAIN entry", @@ -369,6 +368,14 @@ func TestGenerateConfig(t *testing.T) { useDB: true, logPath: nil, }, + { + name: "Query parameters in appendix", + mode: stellarCoreRunnerModeOnline, + appendPath: filepath.Join("testdata", "sample-appendix-query-params.cfg"), + expectedPath: filepath.Join("testdata", "expected-query-params.cfg"), + useDB: true, + logPath: nil, + }, } { t.Run(testCase.name, func(t *testing.T) { var err error @@ -533,3 +540,41 @@ func TestNonDBConfigDoesNotUpdateDatabase(t *testing.T) { require.NoError(t, toml.unmarshal(configBytes, true)) assert.Equal(t, toml.Database, "") } + +func TestHTTPQueryParameters(t *testing.T) { + var err error + var captiveCoreToml *CaptiveCoreToml + httpPort := uint(8000) + peerPort := uint(8000) + logPath := "logPath" + + params := CaptiveCoreTomlParams{ + NetworkPassphrase: "Public Global Stellar Network ; September 2015", + HistoryArchiveURLs: []string{"http://localhost:1170"}, + HTTPPort: &httpPort, + PeerPort: &peerPort, + LogPath: &logPath, + Strict: false, + UseDB: true, + HTTPQueryServerParams: &HTTPQueryServerParams{ + Port: 100, + ThreadPoolSize: 200, + SnapshotLedgers: 300, + }, + } + + captiveCoreToml, err = NewCaptiveCoreToml(params) + assert.NoError(t, err) + + configBytes, err := generateConfig(captiveCoreToml, stellarCoreRunnerModeOffline) + + assert.NoError(t, err) + toml := CaptiveCoreToml{} + require.NoError(t, toml.unmarshal(configBytes, true)) + require.NotNil(t, *toml.HTTPQueryPort) + assert.Equal(t, *toml.HTTPQueryPort, uint(100)) + require.NotNil(t, *toml.QueryThreadPoolSize) + assert.Equal(t, *toml.QueryThreadPoolSize, uint(200)) + require.NotNil(t, *toml.QuerySnapshotLedgers) + assert.Equal(t, *toml.QuerySnapshotLedgers, uint(300)) +} diff --git a/services/horizon/internal/flags_test.go b/services/horizon/internal/flags_test.go index 4d8d352080..a8c5201670 100644 --- a/services/horizon/internal/flags_test.go +++ b/services/horizon/internal/flags_test.go @@ -197,8 +197,8 @@ func TestSetCaptiveCoreConfig(t *testing.T) { CaptiveCoreBinaryPath: "/path/to/captive-core/binary", }, errStr: fmt.Sprintf("invalid captive core toml file: invalid captive core toml: "+ - "NETWORK_PASSPHRASE in captive core config file: %s does not match Horizon "+ - "network-passphrase flag: %s", network.TestNetworkPassphrase, network.PublicNetworkPassphrase), + "NETWORK_PASSPHRASE in captive core config file: %s does not match passed configuration (%s)", + network.TestNetworkPassphrase, network.PublicNetworkPassphrase), }, { name: "no network specified; full captive-core-config not required",