Skip to content
Open
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
10 changes: 5 additions & 5 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,11 @@
path = lib/spdlog
url = https://github.com/gabime/spdlog
ignore = dirty
[submodule "src/protocol-next/xdr"]
path = src/protocol-next/xdr
url = https://github.com/stellar/stellar-xdr
branch = next
#TODO: change name
[submodule "src/protocol-curr/xdr"]
path = src/protocol-curr/xdr
url = https://github.com/stellar/stellar-xdr
branch = curr
branch = main
[submodule "src/rust/soroban/p21"]
path = src/rust/soroban/p21
url = https://github.com/stellar/rs-soroban-env
Expand All @@ -50,6 +47,9 @@
[submodule "src/rust/soroban/p26"]
path = src/rust/soroban/p26
url = https://github.com/stellar/rs-soroban-env.git
[submodule "src/rust/soroban/p27"]
path = src/rust/soroban/p27
url = https://github.com/stellar/rs-soroban-env.git
[submodule "lib/gperftools"]
path = lib/gperftools
url = https://github.com/gperftools/gperftools.git
18 changes: 15 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,24 @@ Steps (tested on Mac OS):

### Special configure flags for unreleased protocol versions

When building with `configure`, the flag below must be used to enable unreleased protocol
versions. If this flag is not provided code and tests relating to the next protocol version will
not execute.
When building with `configure`, the flag below enables all unreleased protocol features.
If not provided, code and tests relating to the next protocol version will not execute.

./configure --enable-next-protocol-version-unsafe-for-production

Individual XDR features can also be enabled independently:

./configure --enable-cap-0071

Each per-feature flag (e.g., `--enable-cap-0071`) does the following:
- Passes `-D<FEATURE>` to the C preprocessor for XDR compilation (xdrc) and C++ code
- Passes `--features <feature>` to the Rust soroban build
- Sets `BUILDING_NEXT_PROTOCOL`, which bumps the protocol version and enables the
next-protocol soroban module (p27)

The meta-flag `--enable-next-protocol-version-unsafe-for-production` enables all individual
feature flags plus `ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION`.

## Sanitizers

Sanitizers are mutually exclusive.
Expand Down
105 changes: 99 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ if USE_POSTGRES
AM_CPPFLAGS += -DUSE_POSTGRES=1 $(libpq_CFLAGS)
endif # USE_POSTGRES

if ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION
AM_CPPFLAGS += -I"$(top_builddir)/src/protocol-next"
else
AM_CPPFLAGS += -I"$(top_builddir)/src/protocol-curr"
endif

# Unconditionally add CEREAL_THREAD_SAFE, we always want it.
AM_CPPFLAGS += -DCEREAL_THREAD_SAFE
Expand All @@ -46,3 +42,15 @@ endif # USE_SPDLOG
if ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION
AM_CPPFLAGS += -DENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION
endif # ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION

if BUILDING_NEXT_PROTOCOL
AM_CPPFLAGS += -DBUILDING_NEXT_PROTOCOL
endif

if ENABLE_CAP_0071
AM_CPPFLAGS += -DCAP_0071
endif

if ENABLE_TEST_FEATURE
AM_CPPFLAGS += -DTEST_FEATURE
endif
18 changes: 18 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,24 @@ AC_ARG_ENABLE(next-protocol-version-unsafe-for-production,
AM_CONDITIONAL(ENABLE_NEXT_PROTOCOL_VERSION_UNSAFE_FOR_PRODUCTION,
[test x$enable_next_protocol_version_unsafe_for_production = xyes])

AC_ARG_ENABLE(cap-0071,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand why do we need detailed per-feature flags, or at least why are they opt-in.
I thought that we still will have a single next protocol flag that would enable the features that have been implemented. We may add flags that disable the features on-demand in case if that helps with downstream integration, though I would imagine it shouldn't matter most of the time.

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.

The idea was to let downstream systems enable exactly what they have support for. What your talking about is somewhat disruptive in that a core update would require downstream to pass in a new flag because it doesn't have support for that feature.

Copy link
Copy Markdown
Contributor

@dmkozh dmkozh Apr 14, 2026

Choose a reason for hiding this comment

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

Downstream systems should generally not create custom core builds and I don't think they should normally pick the features from core - it's up to us to provide a proper build.

I thought the idea is that they can set the feature flags in XDR when necessary in order to avoid supporting stuff like new enum variants etc. while trying to implement some other feature. CAP-71 is actually a good example: building CAP-71 transactions requires rather involved downstream integration, but they can still use a Core build that accepts CAP-71 XDR even if they have none of the support. Since XDR is generally backwards-compatible, I think we should rarely need intermediate builds that disable features, which is why opt-out seems more reasonable than opt-in to me. The main way to break downstream with XDR is likely meta, but it probably could be guarded by a runtime config flag anyways.

Basically I'd like to keep the build configuration as simple as possible. There is seemingly no need to guard CAP-71 on the core side (and of course there shouldn't be a need to guard 'test feature'), so we shouldn't make the configs more complex because of these.

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.

One of the goals of this was to be able to release a subset of the protocol changes whenever we want. Lets say for some reason CAP-71 isn't implemented in core in time, or downstream hasn't adapted in time. We don't want to have to go in and remove or opt out of CAP-71 everywhere. Maybe we need to rethink this priority, but that's the point of this change. CAP-71 isn't as complex, but a more complex CAP that takes a while to implement might not be ready by our self-imposed deadline, which will then need to get pushed. Also re: config-complexity - these flags will be removed once a change is included into a protocol.

You do make an interesting point about changes that downstream should be able to just pass through like CAP-71 though. Maybe in this specific case the ifdefs make less sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One of the goals of this was to be able to release a subset of the protocol changes whenever we want.

That's a pretty general goal that doesn't dictate the implementation. I don't think that encoding releases in build flags is a good idea (do you imagine making a release by just specifying the flags e.g. in Jenkins? that would be really brittle and error-prone). I think that should be driven by checked-in artifacts. Build flags should only be necessary for one-off/integration builds etc.

We don't want to have to go in and remove or opt out of CAP-71 everywhere

Well, that's what we can use a disable flag for. Though again, I would argue a better way would be to merge a respective change that explicitly disables it (e.g. in Config.h or wherever we find suitable). Basically we should have a single canonical definition that says 'p27 is features X,Y,Z'. Then if we do want to disable feature X, we change the definition to 'p27 is features Y,Z'. The question when do we promote feature to be canonical is debatable, but probably it's better to do sooner than later (to avoid integration issues), and then disable if necessary. Also, keep in mind that disabling a feature is supposed to be simple, but I'm sure that there may be more complex situations where disabling it would lead to test failures, which would require a test PR anyways. But I think that's good and still much better than running CI/tests on all the possible feature combinations, or ignoring the features until the very last moment.

You do make an interesting point about changes that downstream should be able to just pass through like CAP-71 though. Maybe in this specific case the ifdefs make less sense.

Yes, disabling the feature for downstream integration is very different from disabling it from the release. The former should be rarely necessary at the core level, and as I've mentioned before we might be better off with runtime flags anyways (like meta). But for the release configuration we need something more stable than a build config flag.

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.

I don't think that encoding releases in build flags is a good idea (do you imagine making a release by just specifying the flags e.g. in Jenkins? that would be really brittle and error-prone)

To be clear. a release should not enable any of these ifdefs. We should remove the ifdefs and feature flags for the CAPs being released, which should happen when we decide to cut the protocol. These flags should be used by downstream teams for integration.

Well, that's what we can use a disable flag for. Though again, I would argue a better way would be to merge a respective change that explicitly disables it (e.g. in Config.h or wherever we find suitable).

Are you saying that a release build will contain these disable flags? That seems error prone, and I don't see how this is easier to automate integration tests with. A code change also isn't desirable because downstream will have to update and pick it up.

But I think that's good and still much better than running CI/tests on all the possible feature combinations, or ignoring the features until the very last moment.

Our CI will continue running for vnext, which will include all features. We won't be using these individual flags in CI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These flags should be used by downstream teams for integration.

To reiterate my point, I'm not convinced downstream teams should do custom core builds, and also shouldn't normally need to customize this at all. We can use these ourselves of course, but again, I'd prefer to avoid this as much as possible until actually necessary (i.e. when we have features that we're concerned about).

Are you saying that a release build will contain these disable flags?

No, these are compile-time flags, aren't they? What I'm suggesting is to keep the single vnext config flag that we use for vnext builds, and add --disable-$feature flags for features that actually need it for on-demand builds (so that you have a non-prod vnext build with a feature disabled explicitly).

A code change also isn't desirable because downstream will have to update and pick it up.

I would argue a code change is desirable and necessary if we want to change the protocol contents. Obviously downstream will have to pick it up (because the protocol has been changed), but that's find and by design, isn't it?

Our CI will continue running for vnext, which will include all features. We won't be using these individual flags in CI.

I think we agree on this point, my main suggestion is to not introduce the flags for the features that don't need to be disabled (and also to switch from opt-in to opt-out, though this is debatable and not as important).

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.

So all of this is an experiment that we can rollback if it doesn't yield what we're looking for. While CAP-0071 doesn't need to be behind a flag for downstream integration, it'd be helpful to see how this flag will flow downstream. Also, disable flags work against what we've been discussing wrt FSR.

It'd also be good to get @Shaptic's input on this, and I'd prefer not to stall on what we're experimenting with here unless there's a good reason.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I thought the point of experiment is to figure out what works/doesn't work. I find the proposed approach concerning. Yes, we can merge this, but I don't see what exactly does this achieve or prove, given that it doesn't seem like the build granularity is not necessary now and I will repeat myself yet again, I believe it's conceptually problematic.
I don't really want to block this, but on the other hand I'm not sure I understand the intended design fully (like 'disable flags work against what we've been discussing wrt FSR' point), so maybe this is worth some kind of a process doc and a team-wide discussion.

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.

To be clear, I don't want to dismiss your feedback, but I'd like to get input from other before deviating from what we have here. We can wait for @Shaptic and @leighmcculloch to chime in before merging.

AS_HELP_STRING([--enable-cap-0071],
[Enable CAP-0071 XDR types and implementation]))
AM_CONDITIONAL(ENABLE_CAP_0071,
[test x$enable_cap_0071 = xyes -o x$enable_next_protocol_version_unsafe_for_production = xyes])

AC_ARG_ENABLE(test-feature,
AS_HELP_STRING([--enable-test-feature],
[Enable TEST_FEATURE XDR types (testing only)]))
AM_CONDITIONAL(ENABLE_TEST_FEATURE,
[test x$enable_test_feature = xyes -o x$enable_next_protocol_version_unsafe_for_production = xyes])

# Derived conditional: true if any next-protocol feature is enabled.
# Add new next-protocol feature flags to this condition.
# Note: TEST_FEATURE is excluded as it is a test fixture, not a protocol feature.
AM_CONDITIONAL(BUILDING_NEXT_PROTOCOL,
[test x$enable_next_protocol_version_unsafe_for_production = xyes -o x$enable_cap_0071 = xyes])
Comment thread
sisuresh marked this conversation as resolved.

AC_PATH_PROG(CARGO, cargo)
if test x"$CARGO" = x; then
AC_MSG_ERROR([cannot find cargo, needed for rust code])
Expand Down
39 changes: 23 additions & 16 deletions docs/versioning-soroban.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,22 +145,29 @@ the upgrade from protocol 22 to 23:
## Dealing with "next"

Stellar-core and soroban-env-host both support the concept of a "next build",
which is a conditional-compilation mode (enabled by a configure flag
`--enable-next-protocol-version-unsafe-for-production`) that supports a protocol
number one-higher than "the one written on the label". I.e. stellar-core v22.0.0
and soroban-env-host v22.0.0 when built with "next" will support something they
call "protocol 23", and will build with XDR from a "next" repo, and so on.

Enabling the "next build" on a given checkout of the tree always shifts the
maximum core-supported protocol up by one. It might also conditionally-include a
work-in-progress "next soroban submodule", or it might simply pass the flag
`--features=next` to the current maximum-numbered soroban submodule (which will
in turn cause that soroban submodule to increment its own max-supported protocol
number).

Which of these two build variants the "next build" causes is controlled by a
variable `WIP_SOROBAN_PROTOCOL` in `src/Makefile.am` and is documented in more
detail there.
which is a conditional-compilation mode that supports a protocol number
one-higher than "the one written on the label".

Next-protocol XDR changes are gated behind `#ifdef` directives (e.g.,
`#ifdef CAP_0071`) in the `.x` files on the `main` branch of `stellar-xdr`.
Individual features can be enabled with per-feature configure flags:

./configure --enable-cap-0071

Or all features can be enabled at once with the meta-flag:

./configure --enable-next-protocol-version-unsafe-for-production

Each per-feature flag sets `BUILDING_NEXT_PROTOCOL`, which:
- Bumps `CURRENT_LEDGER_PROTOCOL_VERSION` by one
- Passes the feature define to both xdrc (for XDR `.h` generation) and C++ code
- Passes `--features next` to the main Rust crate, activating the next soroban
module (e.g., p27)
- Passes `--features <feature>` to the next soroban module's cargo build, which
flows through to `rs-stellar-xdr` to enable the corresponding XDR types

The `WIP_SOROBAN_PROTOCOL` variable in `src/Makefile.am` controls which soroban
submodule is included in next builds. See the comments there for details.

## Rust, Cargo, versions, submodules, rlibs, and dep-tree files

Expand Down
28 changes: 27 additions & 1 deletion hash-xdrs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#
# The goal is to detect the (unfortunately easy) condition of C++ and Rust code
# communicating with each other using different XDR definitions.
#
# Hashes are computed after stripping #ifdef/#endif blocks and removing all
# whitespace, so they are stable regardless of feature ifdefs or formatting.

set -o errexit
set -o pipefail
Expand All @@ -28,7 +31,30 @@ EOF
# Hashes to ignore
IGNORE="Stellar-internal\|Stellar-overlay\|Stellar-contract-spec\|Stellar-contract-meta\|Stellar-contract-env-meta"

sha256sum -b $1/xdr/*.x | grep -v "${IGNORE}" | perl -pe 's/([a-f0-9]+)[ \*]+(.*)/{"$2", "$1"},/'
# Strip #ifdef/#endif blocks (inclusive) and remove all whitespace before
# hashing. This produces a canonical hash of the base XDR content.
# Content between #ifdef and #else (the feature-on branch) is stripped,
# while content between #else and #endif (the feature-off branch) is kept,
# since it represents the base types when no features are enabled.
# Note: this does not hash the feature-gated XDR content. The feature flag
# validation in checkXDRFileIdentity() separately verifies that C++ and Rust
# have the same XDR features enabled.
xdr_hash() {
awk 'BEGIN{skip=0} /#ifdef/{skip=1; next} /#else/{skip=0; next} /#endif/{skip=0; next} skip==0{print}' "$1" \
| tr -d '[:space:]' \
| sha256sum -b \
| cut -d' ' -f1
}

for f in $1/xdr/*.x; do
fname=$(basename "$f")
# Skip ignored files
if echo "$fname" | grep -q "${IGNORE}"; then
continue
fi
hash=$(xdr_hash "$f")
echo "{\"$f\", \"$hash\"},"
done

# Add empty entries for the 5 skipped files
echo '{"", ""}, {"", ""}, {"", ""}, {"", ""}, {"", ""}};'
Expand Down
Loading
Loading