Skip to content

feat: Stellar family support (stellar, stellar-testnet) - #295

Closed
artemrootman wants to merge 5 commits into
mainfrom
arootman/stellar-support-main
Closed

feat: Stellar family support (stellar, stellar-testnet)#295
artemrootman wants to merge 5 commits into
mainfrom
arootman/stellar-support-main

Conversation

@artemrootman

Copy link
Copy Markdown
Contributor

Re-lands #287 onto main: the original PR was stacked on the ton branch (#286), so its merge target was wrong. Same diff, applied onto main with the ripple/ton-only context stripped.

Note: includes the shared DecreasingBoundDetector capability in lower_bounds (the horizon lower bound depends on it); the same change ships in #284, whichever merges second resolves trivially.

Replaces #287.

Re-lands #287 onto main: the original PR was stacked on the ton branch.
Same diff, applied onto main with the ripple/ton-only context stripped;
also carries the shared DecreasingBoundDetector capability the horizon
lower bound depends on (otherwise landing with #284).

- stellar-rpc (json-rpc) and Horizon (rest) as split or combined upstreams
- per-API health/chain validators, labels, lower bounds (horizon bound may
  legally decrease on reingest backfill)
- method specs stellar-json-rpc/stellar-horizon, docs, tests
- bumps pkg/chains/public for the stellar chain entries
…ort-main

# Conflicts:
#	README.md
#	docs/nodecore/05-upstream-config.md
#	docs/nodecore/11-method-specs.md
…ort-main

# Conflicts:
#	README.md
#	docs/nodecore/05-upstream-config.md
#	docs/nodecore/11-method-specs.md
#	internal/upstreams/upstream_factory.go
#	pkg/chains/chains.go

func (s *StellarChainSpecificObject) LabelsProcessor() labels.LabelsProcessor {
var detector labels.ClientLabelsDetector
if s.flavor == stellarHorizon {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need separate specific objects like ton

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - split into StellarRpcChainSpecificObject and StellarHorizonChainSpecificObject with a shared embedded base; the factory picks the object from the primary connector type, same as ton.

}

func (s *StellarChainSpecificObject) CapDetectors(input caps.DetectorInput) []caps.CapDetector {
return caps.DefaultCapDetectors(s.upstreamId, input.WsConnector)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stellar doesn't have WS?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, no WS: stellar-rpc is plain HTTP JSON-RPC (no subscriptions at all), and Horizon streams over SSE, not websocket. CapDetectors returns nil now.

}

func (s *StellarChainSpecificObject) GetFinalizedBlock(ctx context.Context) (protocol.Block, error) {
return s.GetLatestBlock(ctx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest == finalized?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - SCP closes ledgers with immediate finality: a closed ledger is final, there are no reorgs, and both APIs only ever serve closed ledgers, so latest == finalized. Also wired the generic BaseBlockProcessor to poll it, same as ton.

}

func (s *StellarHorizonLowerBoundDetector) DetectLowerBound(ctx context.Context) ([]protocol.LowerBoundData, error) {
elder, err := s.fetchElderLedger(ctx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific method to detect lower bounds? If so, use only it. Otherwise use a simple binary search like in evm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a specific method - Horizon's root document publishes history_elder_ledger directly. The detector now uses only it: one call with retries, no fallback/local bound.

}

func (s *StellarLowerBoundDetector) DetectLowerBound(ctx context.Context) ([]protocol.LowerBoundData, error) {
health, err := s.fetchHealth(ctx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls remove fallbacks, local bound, etc. Just use a simple method with retries

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - single getHealth call with retries (stellar-rpc publishes its retention boundary as oldestLedger), fallback and cached bound removed; on error the processor keeps the last published bound.

internalTimeout time.Duration
}

func NewStellarHealthValidator(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Syncing Validator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - renamed to StellarSyncingValidator.

}
if root.NetworkPassphrase == "" {
log.Error().Err(errStellarHorizonEmptyPassphrase).Msgf("failed to validate the chain of horizon upstream '%s'", s.upstreamId)
return validations.SettingsError

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fatal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - FatalSettingError.

"github.com/rs/zerolog/log"
)

type StellarHorizonHealthValidator struct {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Syncing Validator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - renamed to StellarHorizonSyncingValidator.

// body is not the health document.
var health StellarHorizonHealth
if err := sonic.Unmarshal(response.ResponseResult(), &health); err != nil {
if response.HasError() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get it. It should be before unmarshalling

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reordered - the error check is first now, with the 503-carries-body case as an explicit branch inside it: Horizon answers /health with HTTP 503 while its captive core is syncing, but the body still carries the health booleans, so we parse it to distinguish Syncing from down.

Comment thread pkg/methods/specs/stellar-horizon.json Outdated
"type": "plain"
},
"methods": [
{ "name": "GET#/", "params": [], "settings": { "cacheable": false } },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format json pls

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - both stellar specs reformatted, one field per line.

…le lower bounds

- StellarChainSpecificObject split into StellarRpcChainSpecificObject and
  StellarHorizonChainSpecificObject with a shared embedded base; the factory
  picks the object from the primary connector type (rest => horizon)
- CapDetectors returns nil: stellar-rpc has no ws, horizon streams over SSE
- both objects wire BaseBlockProcessor polling the finalized head (== latest,
  SCP closes ledgers with immediate finality; safe detection disabled)
- both lower bound detectors simplified: one call (getHealth oldestLedger /
  root history_elder_ledger) with retries, no fallback/cached-bound logic;
  horizon keeps AllowsBoundDecrease (db reingest range backfill moves the
  elder ledger down)
- health validators renamed to StellarSyncingValidator and
  StellarHorizonSyncingValidator
- empty network passphrase is FatalSettingError on both APIs
- horizon /health fetch checks the response error before unmarshalling, with
  the 503-carries-body case as an explicit branch
- unused protocolVersion field dropped from the getNetwork parse
- both specs reformatted as plain json, one field per line
- tests for the split objects, bounds and validators
@KirillPamPam KirillPamPam mentioned this pull request Aug 14, 2026
@KirillPamPam

Copy link
Copy Markdown
Collaborator

Done in #336

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants