diff --git a/services/horizon/internal/actions/helpers.go b/services/horizon/internal/actions/helpers.go index 49986c07c3..59d5e8410d 100644 --- a/services/horizon/internal/actions/helpers.go +++ b/services/horizon/internal/actions/helpers.go @@ -345,6 +345,10 @@ func getAsset(r *http.Request, prefix string) (xdr.Asset, error) { func getURLParam(r *http.Request, key string) (string, bool) { rctx := chi.RouteContext(r.Context()) + if rctx == nil { + return "", false + } + // Return immediately if keys does not match Values // This can happen when a named param is not specified. // This is a bug in chi: https://github.com/go-chi/chi/issues/426 diff --git a/services/horizon/internal/actions/root.go b/services/horizon/internal/actions/root.go index 054be6d51f..89857f1906 100644 --- a/services/horizon/internal/actions/root.go +++ b/services/horizon/internal/actions/root.go @@ -5,24 +5,18 @@ import ( "net/url" "github.com/stellar/go/protocols/horizon" + "github.com/stellar/go/services/horizon/internal/corestate" "github.com/stellar/go/services/horizon/internal/ledger" "github.com/stellar/go/services/horizon/internal/resourceadapter" ) -type CoreSettings struct { - Synced bool - CurrentProtocolVersion int32 - CoreSupportedProtocolVersion int32 - CoreVersion string -} - -type CoreSettingsGetter interface { - GetCoreSettings() CoreSettings +type CoreStateGetter interface { + GetCoreState() corestate.State } type GetRootHandler struct { LedgerState *ledger.State - CoreSettingsGetter + CoreStateGetter NetworkPassphrase string FriendbotURL *url.URL HorizonVersion string @@ -37,16 +31,16 @@ func (handler GetRootHandler) GetResource(w HeaderWriter, r *http.Request) (inte "strictReceivePaths": StrictReceivePathsQuery{}.URITemplate(), "strictSendPaths": FindFixedPathsQuery{}.URITemplate(), } - coreSettings := handler.GetCoreSettings() + coreState := handler.GetCoreState() resourceadapter.PopulateRoot( r.Context(), &res, handler.LedgerState.CurrentStatus(), handler.HorizonVersion, - coreSettings.CoreVersion, + coreState.CoreVersion, handler.NetworkPassphrase, - coreSettings.CurrentProtocolVersion, - coreSettings.CoreSupportedProtocolVersion, + coreState.CurrentProtocolVersion, + coreState.CoreSupportedProtocolVersion, handler.FriendbotURL, templates, ) diff --git a/services/horizon/internal/actions/submit_transaction.go b/services/horizon/internal/actions/submit_transaction.go index 11055d829d..13dae57a29 100644 --- a/services/horizon/internal/actions/submit_transaction.go +++ b/services/horizon/internal/actions/submit_transaction.go @@ -19,6 +19,7 @@ import ( type SubmitTransactionHandler struct { Submitter *txsub.System NetworkPassphrase string + CoreStateGetter } type envelopeInfo struct { @@ -136,6 +137,11 @@ func (handler SubmitTransactionHandler) GetResource(w HeaderWriter, r *http.Requ } } + coreState := handler.GetCoreState() + if !coreState.Synced { + return nil, hProblem.StaleHistory + } + submission := handler.Submitter.Submit( r.Context(), info.raw, diff --git a/services/horizon/internal/app.go b/services/horizon/internal/app.go index 617107ee3f..5f2ee91087 100644 --- a/services/horizon/internal/app.go +++ b/services/horizon/internal/app.go @@ -13,8 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stellar/go/clients/stellarcore" - proto "github.com/stellar/go/protocols/stellarcore" - "github.com/stellar/go/services/horizon/internal/actions" + "github.com/stellar/go/services/horizon/internal/corestate" "github.com/stellar/go/services/horizon/internal/db2/history" "github.com/stellar/go/services/horizon/internal/httpx" "github.com/stellar/go/services/horizon/internal/ingest" @@ -30,26 +29,6 @@ import ( "github.com/stellar/go/support/log" ) -type coreSettingsStore struct { - sync.RWMutex - actions.CoreSettings -} - -func (c *coreSettingsStore) set(resp *proto.InfoResponse) { - c.Lock() - defer c.Unlock() - c.Synced = resp.IsSynced() - c.CoreVersion = resp.Info.Build - c.CurrentProtocolVersion = int32(resp.Info.Ledger.Version) - c.CoreSupportedProtocolVersion = int32(resp.Info.ProtocolVersion) -} - -func (c *coreSettingsStore) get() actions.CoreSettings { - c.RLock() - defer c.RUnlock() - return c.CoreSettings -} - // App represents the root of the state of a horizon instance. type App struct { done chan struct{} @@ -60,7 +39,7 @@ type App struct { ctx context.Context cancel func() horizonVersion string - coreSettings coreSettingsStore + coreState corestate.Store orderBookStream *ingest.OrderBookStream submitter *txsub.System paths paths.Finder @@ -80,8 +59,8 @@ type App struct { coreSynced prometheus.GaugeFunc } -func (a *App) GetCoreSettings() actions.CoreSettings { - return a.coreSettings.get() +func (a *App) GetCoreState() corestate.State { + return a.coreState.Get() } const tickerMaxFrequency = 1 * time.Second @@ -390,7 +369,7 @@ func (a *App) UpdateStellarCoreInfo(ctx context.Context) { os.Exit(1) } - a.coreSettings.set(resp) + a.coreState.Set(resp) } // DeleteUnretainedHistory forwards to the app's reaper. See diff --git a/services/horizon/internal/httpx/router.go b/services/horizon/internal/httpx/router.go index 27dd893b84..62d889487a 100644 --- a/services/horizon/internal/httpx/router.go +++ b/services/horizon/internal/httpx/router.go @@ -43,7 +43,7 @@ type RouterConfig struct { MaxPathLength uint PathFinder paths.Finder PrometheusRegistry *prometheus.Registry - CoreGetter actions.CoreSettingsGetter + CoreGetter actions.CoreStateGetter HorizonVersion string FriendbotURL *url.URL HealthCheck http.Handler @@ -124,11 +124,11 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate r.Method(http.MethodGet, "/health", config.HealthCheck) r.Method(http.MethodGet, "/", ObjectActionHandler{Action: actions.GetRootHandler{ - LedgerState: ledgerState, - CoreSettingsGetter: config.CoreGetter, - NetworkPassphrase: config.NetworkPassphrase, - FriendbotURL: config.FriendbotURL, - HorizonVersion: config.HorizonVersion, + LedgerState: ledgerState, + CoreStateGetter: config.CoreGetter, + NetworkPassphrase: config.NetworkPassphrase, + FriendbotURL: config.FriendbotURL, + HorizonVersion: config.HorizonVersion, }}) streamHandler := sse.StreamHandler{ @@ -292,6 +292,7 @@ func (r *Router) addRoutes(config *RouterConfig, rateLimiter *throttled.HTTPRate r.Method(http.MethodPost, "/transactions", ObjectActionHandler{actions.SubmitTransactionHandler{ Submitter: config.TxSubmitter, NetworkPassphrase: config.NetworkPassphrase, + CoreStateGetter: config.CoreGetter, }}) // Network state related endpoints diff --git a/services/horizon/internal/init.go b/services/horizon/internal/init.go index b3b7fe1d44..65cccd5d33 100644 --- a/services/horizon/internal/init.go +++ b/services/horizon/internal/init.go @@ -208,7 +208,7 @@ func initDbMetrics(app *App) { Help: "determines if Stellar-Core defined by --stellar-core-url is synced with the network", }, func() float64 { - if app.coreSettings.Synced { + if app.coreState.Synced { return 1 } else { return 0 diff --git a/services/horizon/internal/render/problem/problem.go b/services/horizon/internal/render/problem/problem.go index f1b23b676e..046c3b6604 100644 --- a/services/horizon/internal/render/problem/problem.go +++ b/services/horizon/internal/render/problem/problem.go @@ -100,8 +100,8 @@ var ( Status: http.StatusServiceUnavailable, Detail: "This horizon instance is configured to reject client requests " + "when it can determine that the history database is lagging too far " + - "behind the connected instance of stellar-core or read replica. Please " + - "try again later.", + "behind the connected instance of Stellar-Core or read replica. It's " + + "also possible that Stellar-Core is out of sync. Please try again later.", } // StillIngesting is a well-known problem type. Use it as a shortcut