Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

services/horizon: Return 503 error in POST /transactions if Stellar-Core not synced #3653

Merged
merged 5 commits into from
Jun 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions services/horizon/internal/actions/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 8 additions & 14 deletions services/horizon/internal/actions/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
)
Expand Down
6 changes: 6 additions & 0 deletions services/horizon/internal/actions/submit_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
type SubmitTransactionHandler struct {
Submitter *txsub.System
NetworkPassphrase string
CoreStateGetter
}

type envelopeInfo struct {
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions services/horizon/internal/actions_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/stellar/go/protocols/horizon"
"github.com/stellar/go/services/horizon/internal/corestate"
"github.com/stellar/go/services/horizon/internal/db2/history"
"github.com/stellar/go/services/horizon/internal/ingest"
"github.com/stellar/go/services/horizon/internal/test"
Expand Down Expand Up @@ -248,6 +249,9 @@ func TestTransactionActions_Post(t *testing.T) {
ht := StartHTTPTest(t, "base")
defer ht.Finish()

// Pass Synced check
ht.App.coreState.SetState(corestate.State{Synced: true})

tx := xdr.TransactionEnvelope{
Type: xdr.EnvelopeTypeEnvelopeTypeTxV0,
V0: &xdr.TransactionV0Envelope{
Expand Down Expand Up @@ -289,6 +293,9 @@ func TestTransactionActions_PostSuccessful(t *testing.T) {
ht := StartHTTPTest(t, "failed_transactions")
defer ht.Finish()

// Pass Synced check
ht.App.coreState.SetState(corestate.State{Synced: true})

destAID := xdr.MustAddress("GBXGQJWVLWOYHFLVTKWV5FGHA3LNYY2JQKM7OAJAUEQFU6LPCSEFVXON")
tx2 := xdr.TransactionEnvelope{
Type: xdr.EnvelopeTypeEnvelopeTypeTxV0,
Expand Down Expand Up @@ -340,6 +347,9 @@ func TestTransactionActions_PostFailed(t *testing.T) {
ht := StartHTTPTest(t, "failed_transactions")
defer ht.Finish()

// Pass Synced check
ht.App.coreState.SetState(corestate.State{Synced: true})

// aa168f12124b7c196c0adaee7c73a64d37f99428cacb59a91ff389626845e7cf
form := url.Values{"tx": []string{"AAAAAG5oJtVdnYOVdZqtXpTHBtbcY0mCmfcBIKEgWnlvFIhaAAAAZAAAAAIAAAACAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAO2C/AO45YBD3tHVFO1R3A0MekP8JR6nN1A9eWidyItUAAAABVVNEAAAAAACuo3ot45qCPExpQ/3oHN+z17Ryis1lfMFYmQWgruS+TAAAAAB3NZQAAAAAAAAAAAFvFIhaAAAAQKcGS9OsVnVHCVIH04C9ZKzzKYBRdCmy+Jwmzld7QcALOxZUcAgkuGfoSdvXpH38mNvrqQiaMsSNmTJWYRzHvgo="}}

Expand All @@ -352,6 +362,10 @@ func TestTransactionActions_PostFailed(t *testing.T) {
func TestPostFeeBumpTransaction(t *testing.T) {
ht := StartHTTPTestWithoutScenario(t)
defer ht.Finish()

// Pass Synced check
ht.App.coreState.SetState(corestate.State{Synced: true})

test.ResetHorizonDB(t, ht.HorizonDB)
q := &history.Q{ht.HorizonSession()}
fixture := history.FeeBumpScenario(ht.T, q, true)
Expand Down Expand Up @@ -387,6 +401,10 @@ func TestPostFeeBumpTransaction(t *testing.T) {
func TestPostFailedFeeBumpTransaction(t *testing.T) {
ht := StartHTTPTestWithoutScenario(t)
defer ht.Finish()

// Pass Synced check
ht.App.coreState.SetState(corestate.State{Synced: true})

test.ResetHorizonDB(t, ht.HorizonDB)
q := &history.Q{ht.HorizonSession()}
fixture := history.FeeBumpScenario(ht.T, q, false)
Expand Down
31 changes: 5 additions & 26 deletions services/horizon/internal/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{}
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions services/horizon/internal/corestate/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package corestate

import (
"sync"

"github.com/stellar/go/protocols/stellarcore"
)

type State struct {
Synced bool
CurrentProtocolVersion int32
CoreSupportedProtocolVersion int32
CoreVersion string
}

type Store struct {
sync.RWMutex
state State
}

func (c *Store) Set(resp *stellarcore.InfoResponse) {
c.Lock()
defer c.Unlock()
c.state.Synced = resp.IsSynced()
c.state.CoreVersion = resp.Info.Build
c.state.CurrentProtocolVersion = int32(resp.Info.Ledger.Version)
c.state.CoreSupportedProtocolVersion = int32(resp.Info.ProtocolVersion)
}

func (c *Store) SetState(state State) {
c.Lock()
defer c.Unlock()
c.state = state
}

func (c *Store) Get() State {
c.RLock()
defer c.RUnlock()
return c.state
}
13 changes: 7 additions & 6 deletions services/horizon/internal/httpx/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion services/horizon/internal/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
bartekn marked this conversation as resolved.
Show resolved Hide resolved
if app.coreState.Get().Synced {
return 1
} else {
return 0
Expand Down
4 changes: 2 additions & 2 deletions services/horizon/internal/render/problem/problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down