diff --git a/exp/services/captivecore/main.go b/exp/services/captivecore/main.go index ef7055da18..677bac375a 100644 --- a/exp/services/captivecore/main.go +++ b/exp/services/captivecore/main.go @@ -20,8 +20,9 @@ import ( func main() { var port int - var networkPassphrase, binaryPath, configPath, dbURL string + var networkPassphrase, binaryPath, configAppendPath, dbURL string var historyArchiveURLs []string + var stellarCoreHTTPPort uint var logLevel logrus.Level logger := supportlog.New() @@ -51,12 +52,12 @@ func main() { ConfigKey: &binaryPath, }, &config.ConfigOption{ - Name: "stellar-core-config-path", + Name: "captive-core-config-append-path", OptType: types.String, FlagDefault: "", Required: false, - Usage: "path to stellar core config file", - ConfigKey: &configPath, + Usage: "path to additional configuration for the Stellar Core configuration file used by captive core. It must, at least, include enough details to define a quorum set", + ConfigKey: &configAppendPath, }, &config.ConfigOption{ Name: "history-archive-urls", @@ -94,6 +95,14 @@ func main() { Required: false, Usage: "horizon postgres database to connect with", }, + &config.ConfigOption{ + Name: "stellar-captive-core-http-port", + ConfigKey: &stellarCoreHTTPPort, + OptType: types.Uint, + FlagDefault: uint(11626), + Required: false, + Usage: "HTTP port for captive core to listen on (0 disables the HTTP server)", + }, } cmd := &cobra.Command{ Use: "captivecore", @@ -104,10 +113,11 @@ func main() { logger.SetLevel(logLevel) captiveConfig := ledgerbackend.CaptiveCoreConfig{ - StellarCoreBinaryPath: binaryPath, - StellarCoreConfigPath: configPath, - NetworkPassphrase: networkPassphrase, - HistoryArchiveURLs: historyArchiveURLs, + BinaryPath: binaryPath, + ConfigAppendPath: configAppendPath, + NetworkPassphrase: networkPassphrase, + HistoryArchiveURLs: historyArchiveURLs, + HTTPPort: stellarCoreHTTPPort, } var dbConn *db.Session diff --git a/exp/tools/captive-core-start-tester/main.go b/exp/tools/captive-core-start-tester/main.go index 4551cadd76..18984818ed 100644 --- a/exp/tools/captive-core-start-tester/main.go +++ b/exp/tools/captive-core-start-tester/main.go @@ -25,10 +25,10 @@ func main() { func check(ledger uint32) bool { c, err := ledgerbackend.NewCaptive( ledgerbackend.CaptiveCoreConfig{ - StellarCoreBinaryPath: "stellar-core", - StellarCoreConfigPath: "stellar-core-standalone2.cfg", - NetworkPassphrase: "Standalone Network ; February 2017", - HistoryArchiveURLs: []string{"http://localhost:1570"}, + BinaryPath: "stellar-core", + ConfigAppendPath: "stellar-core-standalone2.cfg", + NetworkPassphrase: "Standalone Network ; February 2017", + HistoryArchiveURLs: []string{"http://localhost:1570"}, }, ) if err != nil { diff --git a/ingest/doc_test.go b/ingest/doc_test.go index 4e120f07d0..c6ae4de85b 100644 --- a/ingest/doc_test.go +++ b/ingest/doc_test.go @@ -104,10 +104,10 @@ func Example_changes() { // Requires Stellar-Core 13.2.0+ backend, err := ledgerbackend.NewCaptive( ledgerbackend.CaptiveCoreConfig{ - StellarCoreBinaryPath: "/bin/stellar-core", - StellarCoreConfigPath: "/opt/stellar-core.cfg", - NetworkPassphrase: networkPassphrase, - HistoryArchiveURLs: []string{archiveURL}, + BinaryPath: "/bin/stellar-core", + ConfigAppendPath: "/opt/stellar-core.cfg", + NetworkPassphrase: networkPassphrase, + HistoryArchiveURLs: []string{archiveURL}, }, ) if err != nil { diff --git a/ingest/ledgerbackend/captive_core_backend.go b/ingest/ledgerbackend/captive_core_backend.go index 567de34112..95231e2d53 100644 --- a/ingest/ledgerbackend/captive_core_backend.go +++ b/ingest/ledgerbackend/captive_core_backend.go @@ -34,7 +34,7 @@ func roundDownToFirstReplayAfterCheckpointStart(ledger uint32) uint32 { // keep ledger state in RAM. It requires around 3GB of RAM as of August 2020. // * When a UnboundedRange is prepared it runs Stellar-Core catchup mode to // sync with the first ledger and then runs it in a normal mode. This -// requires the configPath to be provided because a database connection is +// requires the configAppendPath to be provided because a database connection is // required and quorum set needs to be selected. // // The database requirement for UnboundedRange will soon be removed when some @@ -62,12 +62,9 @@ func roundDownToFirstReplayAfterCheckpointStart(ledger uint32) uint32 { // // Requires Stellar-Core v13.2.0+. type CaptiveStellarCore struct { - executablePath string - configPath string - networkPassphrase string - historyURLs []string - archive historyarchive.ArchiveInterface - ledgerHashStore TrustedLedgerHashStore + configAppendPath string + archive historyarchive.ArchiveInterface + ledgerHashStore TrustedLedgerHashStore // Quick note on how shutdown works: // If Stellar-Core exits, the exit signal is "catched" by bufferedLedgerMetaReader @@ -79,7 +76,7 @@ type CaptiveStellarCore struct { stellarCoreRunner stellarCoreRunnerInterface // For testing - stellarCoreRunnerFactory func(configPath string) (stellarCoreRunnerInterface, error) + stellarCoreRunnerFactory func(mode stellarCoreRunnerMode, captiveCoreConfigAppendPath string) (stellarCoreRunnerInterface, error) // Defines if the blocking mode (off by default) is on or off. In blocking mode, // calling GetLedger blocks until the requested ledger is available. This is useful @@ -105,21 +102,23 @@ type CaptiveStellarCore struct { // CaptiveCoreConfig contains all the parameters required to create a CaptiveStellarCore instance type CaptiveCoreConfig struct { - // StellarCoreBinaryPath is the file path to the Stellar Core binary - StellarCoreBinaryPath string - // StellarCoreConfigPath is the file path to the Stellar Core configuration file used by captive core - StellarCoreConfigPath string + // BinaryPath is the file path to the Stellar Core binary + BinaryPath string + // ConfigAppendPath is the file path to additional configuration for the Stellar Core configuration file used by captive core + ConfigAppendPath string // NetworkPassphrase is the Stellar network passphrase used by captive core when connecting to the Stellar network NetworkPassphrase string // HistoryArchiveURLs are a list of history archive urls HistoryArchiveURLs []string // LedgerHashStore is an optional store used to obtain hashes for ledger sequences from a trusted source LedgerHashStore TrustedLedgerHashStore + // HTTPPort is the TCP port to listen for requests (defaults to 0, which disables the HTTP server) + HTTPPort uint } // NewCaptive returns a new CaptiveStellarCore. // -// All parameters are required, except configPath which is not required when +// All parameters are required, except configAppendPath which is not required when // working with BoundedRanges only. func NewCaptive(config CaptiveCoreConfig) (*CaptiveStellarCore, error) { archive, err := historyarchive.Connect( @@ -134,19 +133,18 @@ func NewCaptive(config CaptiveCoreConfig) (*CaptiveStellarCore, error) { c := &CaptiveStellarCore{ archive: archive, - executablePath: config.StellarCoreBinaryPath, - configPath: config.StellarCoreConfigPath, - historyURLs: config.HistoryArchiveURLs, - networkPassphrase: config.NetworkPassphrase, + configAppendPath: config.ConfigAppendPath, waitIntervalPrepareRange: time.Second, ledgerHashStore: config.LedgerHashStore, } - c.stellarCoreRunnerFactory = func(configPath2 string) (stellarCoreRunnerInterface, error) { + c.stellarCoreRunnerFactory = func(mode stellarCoreRunnerMode, appendConfigPath string) (stellarCoreRunnerInterface, error) { runner, innerErr := newStellarCoreRunner( - config.StellarCoreBinaryPath, - configPath2, + config.BinaryPath, + appendConfigPath, config.NetworkPassphrase, + config.HTTPPort, config.HistoryArchiveURLs, + mode, ) if innerErr != nil { return runner, innerErr @@ -192,8 +190,8 @@ func (c *CaptiveStellarCore) openOfflineReplaySubprocess(from, to uint32) error } if c.stellarCoreRunner == nil { - // configPath is empty in an offline mode because it's generated - c.stellarCoreRunner, err = c.stellarCoreRunnerFactory("") + // configAppendPath is empty in an offline mode because it's generated + c.stellarCoreRunner, err = c.stellarCoreRunnerFactory(stellarCoreRunnerModeOffline, "") if err != nil { return errors.Wrap(err, "error creating stellar-core runner") } @@ -245,10 +243,7 @@ func (c *CaptiveStellarCore) openOnlineReplaySubprocess(from uint32) error { } if c.stellarCoreRunner == nil { - if c.configPath == "" { - return errors.New("stellar-core config file path cannot be empty in an online mode") - } - c.stellarCoreRunner, err = c.stellarCoreRunnerFactory(c.configPath) + c.stellarCoreRunner, err = c.stellarCoreRunnerFactory(stellarCoreRunnerModeOnline, c.configAppendPath) if err != nil { return errors.Wrap(err, "error creating stellar-core runner") } diff --git a/ingest/ledgerbackend/captive_core_backend_test.go b/ingest/ledgerbackend/captive_core_backend_test.go index 2d364fbedd..9df30ede39 100644 --- a/ingest/ledgerbackend/captive_core_backend_test.go +++ b/ingest/ledgerbackend/captive_core_backend_test.go @@ -148,18 +148,15 @@ func TestCaptiveNew(t *testing.T) { captiveStellarCore, err := NewCaptive( CaptiveCoreConfig{ - StellarCoreBinaryPath: executablePath, - StellarCoreConfigPath: configPath, - NetworkPassphrase: networkPassphrase, - HistoryArchiveURLs: historyURLs, + BinaryPath: executablePath, + ConfigAppendPath: configPath, + NetworkPassphrase: networkPassphrase, + HistoryArchiveURLs: historyURLs, }, ) assert.NoError(t, err) - assert.Equal(t, executablePath, captiveStellarCore.executablePath) - assert.Equal(t, configPath, captiveStellarCore.configPath) - assert.Equal(t, networkPassphrase, captiveStellarCore.networkPassphrase) - assert.Equal(t, historyURLs, captiveStellarCore.historyURLs) + assert.Equal(t, configPath, captiveStellarCore.configAppendPath) assert.Equal(t, uint32(0), captiveStellarCore.nextLedger) assert.NotNil(t, captiveStellarCore.archive) } @@ -189,9 +186,8 @@ func TestCaptivePrepareRange(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -224,9 +220,8 @@ func TestCaptivePrepareRangeCrash(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -256,9 +251,8 @@ func TestCaptivePrepareRangeTerminated(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -272,7 +266,6 @@ func TestCaptivePrepareRange_ErrClosingSession(t *testing.T) { mockRunner.On("close").Return(fmt.Errorf("transient error")) captiveBackend := CaptiveStellarCore{ - networkPassphrase: network.PublicNetworkPassphrase, nextLedger: 300, stellarCoreRunner: mockRunner, } @@ -294,8 +287,7 @@ func TestCaptivePrepareRange_ErrGettingRootHAS(t *testing.T) { Return(historyarchive.HistoryArchiveState{}, errors.New("transient error")) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, + archive: mockArchive, } err := captiveBackend.PrepareRange(BoundedRange(100, 200)) @@ -317,8 +309,7 @@ func TestCaptivePrepareRange_FromIsAheadOfRootHAS(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, + archive: mockArchive, } err := captiveBackend.PrepareRange(BoundedRange(100, 200)) @@ -341,9 +332,8 @@ func TestCaptivePrepareRange_ToIsAheadOfRootHAS(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -365,9 +355,8 @@ func TestCaptivePrepareRange_ErrCatchup(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -394,8 +383,7 @@ func TestCaptivePrepareRangeUnboundedRange_ErrGettingRootHAS(t *testing.T) { Return(historyarchive.HistoryArchiveState{}, errors.New("transient error")) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, + archive: mockArchive, } err := captiveBackend.PrepareRange(UnboundedRange(100)) @@ -414,8 +402,7 @@ func TestCaptivePrepareRangeUnboundedRange_FromIsTooFarAheadOfLatestHAS(t *testi }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, + archive: mockArchive, } err := captiveBackend.PrepareRange(UnboundedRange(193)) @@ -439,10 +426,9 @@ func TestCaptivePrepareRangeUnboundedRange_ErrRunFrom(t *testing.T) { Return(xdr.LedgerHeaderHistoryEntry{}, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - configPath: "foo", - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + configAppendPath: "foo", + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -468,7 +454,6 @@ func TestCaptivePrepareRangeUnboundedRange_ErrClosingExistingSession(t *testing. last := uint32(63) captiveBackend := CaptiveStellarCore{ - networkPassphrase: network.PublicNetworkPassphrase, nextLedger: 63, lastLedger: &last, stellarCoreRunner: mockRunner, @@ -502,10 +487,9 @@ func TestCaptivePrepareRangeUnboundedRange_ReuseSession(t *testing.T) { Return(xdr.LedgerHeaderHistoryEntry{}, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - configPath: "foo", - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + configAppendPath: "foo", + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -545,10 +529,9 @@ func TestGetLatestLedgerSequence(t *testing.T) { Return(xdr.LedgerHeaderHistoryEntry{}, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - configPath: "foo", - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + configAppendPath: "foo", + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -603,9 +586,8 @@ func TestCaptiveGetLedger(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -668,9 +650,8 @@ func TestCaptiveGetLedger_NextLedgerIsDifferentToLedgerFromBuffer(t *testing.T) }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -699,9 +680,8 @@ func TestCaptiveGetLedger_ErrReadingMetaResult(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -737,9 +717,8 @@ func TestCaptiveGetLedger_ErrClosingAfterLastLedger(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -778,9 +757,8 @@ func TestCaptiveGetLedger_BoundedGetLedgerAfterCoreExit(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -830,10 +808,9 @@ func TestCaptiveGetLedger_CloseBufferFull(t *testing.T) { Return(xdr.LedgerHeaderHistoryEntry{}, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - configPath: "foo", - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + configAppendPath: "foo", + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -888,9 +865,8 @@ func TestGetLedgerBoundsCheck(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -959,9 +935,8 @@ func TestCaptiveGetLedgerTerminated(t *testing.T) { }, nil) captiveBackend := CaptiveStellarCore{ - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, } @@ -1006,7 +981,6 @@ func TestCaptiveUseOfLedgerHashStore(t *testing.T) { captiveBackend := CaptiveStellarCore{ archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, stellarCoreRunner: mockRunner, ledgerHashStore: mockLedgerHashStore, } @@ -1084,7 +1058,6 @@ func TestCaptiveRunFromParams(t *testing.T) { captiveBackend := CaptiveStellarCore{ archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, stellarCoreRunner: mockRunner, } @@ -1191,10 +1164,9 @@ func TestCaptivePreviousLedgerCheck(t *testing.T) { defer mockLedgerHashStore.AssertExpectations(t) captiveBackend := CaptiveStellarCore{ - configPath: "stellar-core.cfg", - archive: mockArchive, - networkPassphrase: network.PublicNetworkPassphrase, - stellarCoreRunnerFactory: func(configPath string) (stellarCoreRunnerInterface, error) { + configAppendPath: "stellar-core.cfg", + archive: mockArchive, + stellarCoreRunnerFactory: func(_ stellarCoreRunnerMode, _ string) (stellarCoreRunnerInterface, error) { return mockRunner, nil }, ledgerHashStore: mockLedgerHashStore, diff --git a/ingest/ledgerbackend/stellar_core_runner.go b/ingest/ledgerbackend/stellar_core_runner.go index de99c5e571..e42c426d65 100644 --- a/ingest/ledgerbackend/stellar_core_runner.go +++ b/ingest/ledgerbackend/stellar_core_runner.go @@ -15,6 +15,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/stellar/go/support/log" ) @@ -30,11 +31,20 @@ type stellarCoreRunnerInterface interface { close() error } +type stellarCoreRunnerMode int + +const ( + stellarCoreRunnerModeOnline stellarCoreRunnerMode = iota + stellarCoreRunnerModeOffline +) + type stellarCoreRunner struct { executablePath string - configPath string + configAppendPath string networkPassphrase string historyURLs []string + httpPort uint + mode stellarCoreRunnerMode started bool wg sync.WaitGroup @@ -54,7 +64,7 @@ type stellarCoreRunner struct { Log *log.Entry } -func newStellarCoreRunner(executablePath, configPath, networkPassphrase string, historyURLs []string) (*stellarCoreRunner, error) { +func newStellarCoreRunner(executablePath, coreConfigAppendPath, networkPassphrase string, httpPort uint, historyURLs []string, mode stellarCoreRunnerMode) (*stellarCoreRunner, error) { r := rand.New(rand.NewSource(time.Now().UnixNano())) // Create temp dir @@ -67,9 +77,11 @@ func newStellarCoreRunner(executablePath, configPath, networkPassphrase string, runner := &stellarCoreRunner{ executablePath: executablePath, - configPath: configPath, + configAppendPath: coreConfigAppendPath, networkPassphrase: networkPassphrase, historyURLs: historyURLs, + httpPort: httpPort, + mode: mode, shutdown: make(chan struct{}), processExit: make(chan struct{}), processExitError: nil, @@ -77,43 +89,56 @@ func newStellarCoreRunner(executablePath, configPath, networkPassphrase string, nonce: fmt.Sprintf("captive-stellar-core-%x", r.Uint64()), } - if configPath == "" { - err := runner.writeConf() - if err != nil { - return nil, errors.Wrap(err, "error writing configuration") - } + if err := runner.writeConf(); err != nil { + return nil, errors.Wrap(err, "error writing configuration") } return runner, nil } -func (r *stellarCoreRunner) generateConfig() string { +func (r *stellarCoreRunner) generateConfig() (string, error) { + if r.mode == stellarCoreRunnerModeOnline && r.configAppendPath == "" { + return "", errors.New("stellar-core append config file path cannot be empty in online mode") + } lines := []string{ "# Generated file -- do not edit", - "RUN_STANDALONE=true", "NODE_IS_VALIDATOR=false", "DISABLE_XDR_FSYNC=true", - "UNSAFE_QUORUM=true", fmt.Sprintf(`NETWORK_PASSPHRASE="%s"`, r.networkPassphrase), fmt.Sprintf(`BUCKET_DIR_PATH="%s"`, filepath.Join(r.tempDir, "buckets")), + fmt.Sprintf(`HTTP_PORT=%d`, r.httpPort), + } + + if r.mode == stellarCoreRunnerModeOffline { + // In offline mode, there is no need to connect to peers + lines = append(lines, "RUN_STANDALONE=true") + // We don't need consensus when catching up + lines = append(lines, "UNSAFE_QUORUM=true") } for i, val := range r.historyURLs { lines = append(lines, fmt.Sprintf("[HISTORY.h%d]", i)) lines = append(lines, fmt.Sprintf(`get="curl -sf %s/{0} -o {1}"`, val)) } - // Add a fictional quorum -- necessary to convince core to start up; - // but not used at all for our purposes. Pubkey here is just random. - lines = append(lines, - "[QUORUM_SET]", - "THRESHOLD_PERCENT=100", - `VALIDATORS=["GCZBOIAY4HLKAJVNJORXZOZRAY2BJDBZHKPBHZCRAIUR5IHC2UHBGCQR"]`) - return strings.ReplaceAll(strings.Join(lines, "\n"), "\\", "\\\\") + if r.mode == stellarCoreRunnerModeOffline && r.configAppendPath == "" { + // Add a fictional quorum -- necessary to convince core to start up; + // but not used at all for our purposes. Pubkey here is just random. + lines = append(lines, + "[QUORUM_SET]", + "THRESHOLD_PERCENT=100", + `VALIDATORS=["GCZBOIAY4HLKAJVNJORXZOZRAY2BJDBZHKPBHZCRAIUR5IHC2UHBGCQR"]`) + } + result := strings.ReplaceAll(strings.Join(lines, "\n"), "\\", "\\\\") + if r.configAppendPath != "" { + appendConfigContents, err := ioutil.ReadFile(r.configAppendPath) + if err != nil { + return "", errors.Wrap(err, "reading quorum config file") + } + result = result + "\n" + string(appendConfigContents) + } + return result, nil } func (r *stellarCoreRunner) getConfFileName() string { - if r.configPath != "" { - return r.configPath - } return filepath.Join(r.tempDir, "stellar-core.conf") } @@ -173,7 +198,11 @@ func (r *stellarCoreRunner) getLogLineWriter() io.Writer { // Makes the temp directory and writes the config file to it; called by the // platform-specific captiveStellarCore.Start() methods. func (r *stellarCoreRunner) writeConf() error { - conf := r.generateConfig() + conf, err := r.generateConfig() + if err != nil { + return err + } + r.Log.Debugf("captive core config file contents:\n%s", conf) return ioutil.WriteFile(r.getConfFileName(), []byte(conf), 0644) } diff --git a/ingest/ledgerbackend/stellar_core_runner_test.go b/ingest/ledgerbackend/stellar_core_runner_test.go new file mode 100644 index 0000000000..b5ac99d117 --- /dev/null +++ b/ingest/ledgerbackend/stellar_core_runner_test.go @@ -0,0 +1,33 @@ +package ledgerbackend + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateConfig(t *testing.T) { + tmpFile, err := ioutil.TempFile("", "test-generate-config") + assert.NoError(t, err) + defer tmpFile.Close() + defer os.Remove(tmpFile.Name()) + + r := stellarCoreRunner{ + configAppendPath: tmpFile.Name(), + } + + tmpFile.WriteString(`[[HOME_DOMAINS]] +HOME_DOMAIN="testnet.stellar.org" +QUALITY="HIGH" + +[[VALIDATORS]] +NAME="sdf_testnet_1" +HOME_DOMAIN="testnet.stellar.org" +PUBLIC_KEY="GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" +ADDRESS="core-testnet1.stellar.org" +`) + _, err = r.generateConfig() + assert.NoError(t, err) +} diff --git a/services/horizon/cmd/db.go b/services/horizon/cmd/db.go index 13d1aab522..9bc41d862f 100644 --- a/services/horizon/cmd/db.go +++ b/services/horizon/cmd/db.go @@ -225,7 +225,7 @@ var dbReingestRangeCmd = &cobra.Command{ MaxReingestRetries: int(retries), ReingestRetryBackoffSeconds: int(retryBackoffSeconds), EnableCaptiveCore: config.EnableCaptiveCoreIngestion, - StellarCoreBinaryPath: config.StellarCoreBinaryPath, + CaptiveCoreBinaryPath: config.CaptiveCoreBinaryPath, RemoteCaptiveCoreURL: config.RemoteCaptiveCoreURL, } diff --git a/services/horizon/cmd/ingest.go b/services/horizon/cmd/ingest.go index b7032f0976..8bec6706b1 100644 --- a/services/horizon/cmd/ingest.go +++ b/services/horizon/cmd/ingest.go @@ -103,7 +103,7 @@ var ingestVerifyRangeCmd = &cobra.Command{ HistorySession: horizonSession, HistoryArchiveURL: config.HistoryArchiveURLs[0], EnableCaptiveCore: config.EnableCaptiveCoreIngestion, - StellarCoreBinaryPath: config.StellarCoreBinaryPath, + CaptiveCoreBinaryPath: config.CaptiveCoreBinaryPath, RemoteCaptiveCoreURL: config.RemoteCaptiveCoreURL, } @@ -191,7 +191,7 @@ var ingestStressTestCmd = &cobra.Command{ } if config.EnableCaptiveCoreIngestion { - ingestConfig.StellarCoreBinaryPath = config.StellarCoreBinaryPath + ingestConfig.CaptiveCoreBinaryPath = config.CaptiveCoreBinaryPath ingestConfig.RemoteCaptiveCoreURL = config.RemoteCaptiveCoreURL } else { if config.StellarCoreDatabaseURL == "" { @@ -273,7 +273,7 @@ var ingestInitGenesisStateCmd = &cobra.Command{ } if config.EnableCaptiveCoreIngestion { - ingestConfig.StellarCoreBinaryPath = config.StellarCoreBinaryPath + ingestConfig.CaptiveCoreBinaryPath = config.CaptiveCoreBinaryPath } else { if config.StellarCoreDatabaseURL == "" { log.Fatalf("flag --%s cannot be empty", horizon.StellarCoreDBURLFlagName) diff --git a/services/horizon/internal/config.go b/services/horizon/internal/config.go index 51e1ce2761..83f295f1e9 100644 --- a/services/horizon/internal/config.go +++ b/services/horizon/internal/config.go @@ -16,12 +16,14 @@ type Config struct { Port uint AdminPort uint - EnableCaptiveCoreIngestion bool - StellarCoreBinaryPath string - StellarCoreConfigPath string - StellarCoreDatabaseURL string - StellarCoreURL string - RemoteCaptiveCoreURL string + EnableCaptiveCoreIngestion bool + CaptiveCoreBinaryPath string + CaptiveCoreConfigAppendPath string + RemoteCaptiveCoreURL string + CaptiveCoreHTTPPort uint + + StellarCoreDatabaseURL string + StellarCoreURL string // MaxDBConnections has a priority over all 4 values below. MaxDBConnections int diff --git a/services/horizon/internal/docs/captive_core.md b/services/horizon/internal/docs/captive_core.md index 89f03d0de6..90ae1e69e6 100644 --- a/services/horizon/internal/docs/captive_core.md +++ b/services/horizon/internal/docs/captive_core.md @@ -13,7 +13,22 @@ Captive Stellar-Core can be used in both reingestion and normal Horizon operatio To enable captive mode three feature config variables are required: * `ENABLE_CAPTIVE_CORE_INGESTION=true`, * `STELLAR_CORE_BINARY_PATH` - defines a path to the `stellar-core` binary, -* `STELLAR_CORE_CONFIG_PATH` - defines a path to the `stellar-core.cfg` file (not required when reingesting). +* `CAPTIVE_CORE_CONFIG_APPEND_PATH` - (not required when running `horizon db reingest range`) defines a path to a file to append to the Stellar Core configuration file used by captive core. It must, at least, include enough details to define a quorum set. For instance, to connect to the Stellar testnet through `core-testnet1.stellar.org`: + ``` + [[HOME_DOMAINS]] + HOME_DOMAIN="testnet.stellar.org" + QUALITY="MEDIUM" + + [[VALIDATORS]] + NAME="sdf_testnet_1" + HOME_DOMAIN="testnet.stellar.org" + PUBLIC_KEY="GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y" + ADDRESS="core-testnet1.stellar.org" + ``` + + The full configuration to be used will be printed out by Horizon when runnign horizon with `--log-level debug` + +* (optional) `CAPTIVE_CORE_HTTP_PORT` - HTTP port for Captive Core to listen on (0 disables the HTTP server) ### Requirements diff --git a/services/horizon/internal/flags.go b/services/horizon/internal/flags.go index dd392c5814..4123ddb94d 100644 --- a/services/horizon/internal/flags.go +++ b/services/horizon/internal/flags.go @@ -1,6 +1,7 @@ package horizon import ( + "fmt" "go/types" stdLog "log" "os" @@ -23,6 +24,10 @@ const ( StellarCoreDBURLFlagName = "stellar-core-db-url" // StellarCoreDBURLFlagName is the command line flag for configuring the URL fore Stellar Core HTTP endpoint StellarCoreURLFlagName = "stellar-core-url" + // StellarCoreBinaryPathName is the command line flag for configuring the path to the stellar core binary + StellarCoreBinaryPathName = "stellar-core-binary-path" + // CaptiveCoreConfigAppendPathName is the command line flag for configuring the path to the captive core additional configuration + CaptiveCoreConfigAppendPathName = "captive-core-config-append-path" ) // validateBothOrNeither ensures that both options are provided, if either is provided. @@ -86,12 +91,12 @@ func Flags() (*Config, support.ConfigOptions) { Usage: "horizon postgres database to connect with", }, &support.ConfigOption{ - Name: "stellar-core-binary-path", + Name: StellarCoreBinaryPathName, OptType: types.String, FlagDefault: "", Required: false, Usage: "path to stellar core binary (--remote-captive-core-url has higher precedence)", - ConfigKey: &config.StellarCoreBinaryPath, + ConfigKey: &config.CaptiveCoreBinaryPath, }, &support.ConfigOption{ Name: "remote-captive-core-url", @@ -102,21 +107,29 @@ func Flags() (*Config, support.ConfigOptions) { ConfigKey: &config.RemoteCaptiveCoreURL, }, &support.ConfigOption{ - Name: "stellar-core-config-path", + Name: CaptiveCoreConfigAppendPathName, OptType: types.String, FlagDefault: "", Required: false, - Usage: "path to stellar core config file", - ConfigKey: &config.StellarCoreConfigPath, + Usage: "path to additional configuration for the Stellar Core configuration file used by captive core. It must, at least, include enough details to define a quorum set", + ConfigKey: &config.CaptiveCoreConfigAppendPath, }, &support.ConfigOption{ Name: "enable-captive-core-ingestion", OptType: types.Bool, FlagDefault: false, Required: false, - Usage: "[experimental flag!] causes Horizon to ingest from a Stellar Core subprocess instead of a persistent Stellar Core database", + Usage: "[experimental flag!] causes Horizon to ingest from a Stellar Core process instead of a persistent Stellar Core database", ConfigKey: &config.EnableCaptiveCoreIngestion, }, + &support.ConfigOption{ + Name: "captive-core-http-port", + OptType: types.Uint, + FlagDefault: uint(11626), + Required: false, + Usage: "HTTP port for Captive Core to listen on (0 disables the HTTP server)", + ConfigKey: &config.CaptiveCoreHTTPPort, + }, &support.ConfigOption{ Name: StellarCoreDBURLFlagName, EnvVar: "STELLAR_CORE_DATABASE_URL", @@ -129,7 +142,7 @@ func Flags() (*Config, support.ConfigOptions) { Name: StellarCoreURLFlagName, ConfigKey: &config.StellarCoreURL, OptType: types.String, - Usage: "stellar-core to connect with (for http commands)", + Usage: "stellar-core to connect with (for http commands). If unset and the local Captive core is enabled, it will use http://localhost:", }, &support.ConfigOption{ Name: "history-archive-urls", @@ -394,15 +407,20 @@ func ApplyFlags(config *Config, flags support.ConfigOptions) { } if config.EnableCaptiveCoreIngestion { - binaryPath := viper.GetString("stellar-core-binary-path") + binaryPath := viper.GetString(StellarCoreBinaryPathName) remoteURL := viper.GetString("remote-captive-core-url") if binaryPath == "" && remoteURL == "" { stdLog.Fatalf("Invalid config: captive core requires that either --stellar-core-binary-path or --remote-captive-core-url is set") } + if config.StellarCoreURL == "" && config.RemoteCaptiveCoreURL == "" && config.CaptiveCoreHTTPPort != 0 { + // If we don't supply an explicit core URL and we are running a local captive core process + // with the http port enabled, point to it. + config.StellarCoreURL = fmt.Sprintf("http://localhost:%d", config.CaptiveCoreHTTPPort) + } } if config.Ingest { // When running live ingestion a config file is required too - validateBothOrNeither("stellar-core-binary-path", "stellar-core-config-path") + validateBothOrNeither(StellarCoreBinaryPathName, CaptiveCoreConfigAppendPathName) } // Configure log file diff --git a/services/horizon/internal/ingest/main.go b/services/horizon/internal/ingest/main.go index 3f7dfd68cc..c63e4b63a1 100644 --- a/services/horizon/internal/ingest/main.go +++ b/services/horizon/internal/ingest/main.go @@ -63,14 +63,15 @@ const ( var log = logpkg.DefaultLogger.WithField("service", "ingest") type Config struct { - CoreSession *db.Session - StellarCoreURL string - StellarCoreCursor string - EnableCaptiveCore bool - StellarCoreBinaryPath string - StellarCoreConfigPath string - RemoteCaptiveCoreURL string - NetworkPassphrase string + CoreSession *db.Session + StellarCoreURL string + StellarCoreCursor string + EnableCaptiveCore bool + CaptiveCoreBinaryPath string + CaptiveCoreConfigAppendPath string + CaptiveCoreHTTPPort uint + RemoteCaptiveCoreURL string + NetworkPassphrase string HistorySession *db.Session HistoryArchiveURL string @@ -177,11 +178,12 @@ func NewSystem(config Config) (System, error) { var captiveCoreBackend *ledgerbackend.CaptiveStellarCore captiveCoreBackend, err = ledgerbackend.NewCaptive( ledgerbackend.CaptiveCoreConfig{ - StellarCoreBinaryPath: config.StellarCoreBinaryPath, - StellarCoreConfigPath: config.StellarCoreConfigPath, - NetworkPassphrase: config.NetworkPassphrase, - HistoryArchiveURLs: []string{config.HistoryArchiveURL}, - LedgerHashStore: ledgerbackend.NewHorizonDBLedgerHashStore(config.HistorySession), + BinaryPath: config.CaptiveCoreBinaryPath, + ConfigAppendPath: config.CaptiveCoreConfigAppendPath, + HTTPPort: config.CaptiveCoreHTTPPort, + NetworkPassphrase: config.NetworkPassphrase, + HistoryArchiveURLs: []string{config.HistoryArchiveURL}, + LedgerHashStore: ledgerbackend.NewHorizonDBLedgerHashStore(config.HistorySession), }, ) if err != nil { diff --git a/services/horizon/internal/init.go b/services/horizon/internal/init.go index cd22b6cef9..8976797a52 100644 --- a/services/horizon/internal/init.go +++ b/services/horizon/internal/init.go @@ -64,14 +64,15 @@ func initExpIngester(app *App) { // TODO: // Use the first archive for now. We don't have a mechanism to // use multiple archives at the same time currently. - HistoryArchiveURL: app.config.HistoryArchiveURLs[0], - StellarCoreURL: app.config.StellarCoreURL, - StellarCoreCursor: app.config.CursorName, - StellarCoreBinaryPath: app.config.StellarCoreBinaryPath, - StellarCoreConfigPath: app.config.StellarCoreConfigPath, - RemoteCaptiveCoreURL: app.config.RemoteCaptiveCoreURL, - EnableCaptiveCore: app.config.EnableCaptiveCoreIngestion, - DisableStateVerification: app.config.IngestDisableStateVerification, + HistoryArchiveURL: app.config.HistoryArchiveURLs[0], + StellarCoreURL: app.config.StellarCoreURL, + StellarCoreCursor: app.config.CursorName, + CaptiveCoreBinaryPath: app.config.CaptiveCoreBinaryPath, + CaptiveCoreConfigAppendPath: app.config.CaptiveCoreConfigAppendPath, + CaptiveCoreHTTPPort: app.config.CaptiveCoreHTTPPort, + RemoteCaptiveCoreURL: app.config.RemoteCaptiveCoreURL, + EnableCaptiveCore: app.config.EnableCaptiveCoreIngestion, + DisableStateVerification: app.config.IngestDisableStateVerification, }) if err != nil {