Skip to content

refactor: node mode two regimes - #5596

Open
martinconic wants to merge 29 commits into
masterfrom
refactor/node-mode-two-regimes
Open

martinconic wants to merge 29 commits into
masterfrom
refactor/node-mode-two-regimes

Conversation

@martinconic

@martinconic martinconic commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Checklist

  • I have read the coding guide.
  • My change requires a documentation update, and I have done it.
  • I have added tests to cover my changes.
  • I have filled out the description and linked the related issues.

Description

Closes #5172. Supersedes #5431, which this PR carries in full, and addresses the upgrade-path review @gacevicljubisa left there.

Bee's operational mode used to be implicit: full-node: true gave a full node, otherwise a blockchain-rpc-endpoint gave a light node, otherwise ultra-light. This PR adds an explicit node-mode option (full, light, ultra-light) and deprecates full-node. Existing configs keep working unchanged: which behaviour applies depends only on whether node-mode is set.

node-mode unset (legacy): behaves as master does today.

  • The mode is inferred as before.
  • chequebook-enable and storage-incentives-enable keep their default of true. NewBee still gates them (chequebook on swap, incentives on full non-bootnode), so nothing starts that master would not start.
  • Configs are rejected exactly when master rejects them, only earlier (see below). A full node without an RPC endpoint now fails with a clear message instead of at chain init.
  • One warning names the inferred mode, the release that removes the inference (v2.11.0, a constant in start.go), and the exact options to set and remove so the node keeps its behavior once node-mode is set. For example, a legacy full node without swap gets set=[node-mode=full, swap-enable=false] remove=[full-node]; a light node carrying storage-incentives-enable: true is told to remove it. Where behavior cannot be preserved (chequebook-verification without swap), a behavior_change field says what changes and what to choose.

node-mode set: the mode owns the config.

  • full requires an RPC endpoint and implies swap-enable, chequebook-enable and storage-incentives-enable. An explicit false is honored and logged at Info level, since it is a deliberate choice (the legacy migration may suggest it).
  • Full bootnodes are exempt from the implied options: NewBee never runs the reserve or the incentives agent on a bootnode. Swap starts on a bootnode only when swap-enable is set explicitly.
  • light requires an RPC endpoint. Swap stays off unless set, as for light nodes on master: the base light node buys stamps and uploads and downloads within the free pseudosettle allowance, without a chequebook or funds at startup. When swap is off it logs, at Info, that uploads and downloads are limited to that allowance and that swap-enable: true with a funded chequebook exceeds it (the light node described in the docs). storage-incentives-enable: true is rejected.
  • ultra-light rejects swap-enable: true, chequebook-enable: true and storage-incentives-enable: true. A configured RPC endpoint is ignored, with a warning.
  • In every mode, an unset chequebook-enable follows swap-enable. An explicit chequebook-enable: true with swap off is rejected as a contradiction.
  • full-node set alongside node-mode is ignored, with a warning.
  • The node-mode value is matched case-insensitively and ignoring surrounding whitespace (FULL works; a whitespace-only value is rejected). Surrounding whitespace in blockchain-rpc-endpoint is trimmed, and a whitespace-only endpoint counts as unset.
  • A full bootnode with storage-incentives-enable: true starts with a warning that the option has no effect there.

Invalid configs exit with an error, in both regimes. Node mode is resolved and validated in the start command before the node is built, so a rejected config prints Error: … and exits with status 1, before the keystore is unlocked. Besides the per-mode rules above, these checks moved there from NewBee (which keeps its copies for other callers): a bootnode must be full, reserve-capacity-doubling only on full nodes, swap needs a chain, and chequebook-verification needs a full node with chequebook and chain. Previously these errors were only logged and the process exited 0. Other errors raised inside NewBee (for example an unreachable RPC endpoint) are unchanged.

pkg/node: Options.FullNodeMode is replaced by Options.NodeMode. The chain backend is enabled only for full and light, so an unset or unknown mode can no longer enable it. /node reports the configured mode.

Flags: node-mode defaults to empty, so unset selects the legacy behaviour without relying on viper's IsSet semantics for pflag defaults. full-node is marked deprecated.

Packaging: BEE_NODE_MODE added to the docker-compose passthrough and the env template, BEE_FULL_NODE marked deprecated, and the four bee.yaml variants reorganised into sections with a node-mode section documenting the modes, the bootnode exemption and the deprecated inference.

Behaviour

Resolved values, as produced through the real start flags:

Config Mode swap chequebook incentives
legacy — no options ultra-light false true true
legacy — RPC + swap-enable: true light true true true
legacy — chequebook-enable: true, no swap ultra-light false true true
legacy — full-node: true + RPC full false true true
legacy — full-node + bootnode-mode + RPC + incentives false full false true false
legacy — full-node: true, no RPC error: full node requires blockchain-rpc-endpoint
legacy — swap-enable: true, no RPC error: swap is enabled but the chain backend is not
legacy — RPC + reserve-capacity-doubling: 1 error: reserve capacity doubling is only allowed for full nodes
full + RPC full true true true
full + RPC + incentives false full true true false (Info)
full + RPC + swap false full false false true
full + bootnode-mode + RPC full false false false
full + bootnode-mode + RPC + swap true full true true false
full, no RPC error: full node requires blockchain-rpc-endpoint
light + RPC light (Info: free allowance only) false false false
light + RPC + swap true light true true false
light + RPC + chequebook true error: chequebook-enable requires swap-enable
light + bootnode-mode error: boot node must be started as a full node
ultra-light ultra-light false false false
ultra-light + RPC ultra-light (RPC ignored, warning) false false false
ultra-light + swap or chequebook true error: ultra-light node cannot have … set to true

The legacy rows match master: the same configs start with the same values, and the same configs are rejected, now earlier and with exit status 1. As on master, NewBee still starts the chequebook only when swap is on, and the incentives agent only on a full non-bootnode.

Upgrade notes

  • Migrating to node-mode: apply everything the deprecation warning lists under set and remove, not only the node-mode line. Adding node-mode: full alone to a legacy full node without swap turns on swap and deploys a chequebook (a gas transaction; startup waits while the wallet has no gas), and configs carrying the old defaults explicitly (storage-incentives-enable: true on light/ultra-light, chequebook-enable: true without swap) are rejected.
  • Rollback: releases up to v2.8.3 reject unknown config keys, so a config containing node-mode must drop it before downgrading.
  • Exit status: configs these checks reject now exit 1 instead of 0, including legacy ones. Under Restart=on-failure (systemd) or Docker on-failure, a misconfigured node restarts instead of stopping.
  • API: with node-mode set, chequebook-enable follows swap-enable, so /node reports chequebookEnabled: false for nodes without swap and /chequebook/* returns 403 on them. Legacy nodes without swap report true while no chequebook runs.

Test plan

  • TestResolveNodeMode covers both behaviours case by case. TestResolveNodeModeWithBoundFlags runs the resolver against the real bound start flags, so the empty node-mode default and the true defaults of the sub-options are exercised as in production, including bootnodes and ultra-light rejections. TestResolveNodeModeWarnings asserts the warnings, including the migration's set/remove lists. TestResolveNodeModeTrimsRPCEndpoint covers endpoint trimming. TestIsChainEnabled covers unset and unknown modes.
  • TestLegacyMigrationPreservesBehavior applies the warning's migration to all 648 legacy combinations of bootnode-mode × RPC × full-node × swap/chequebook/incentives × chequebook-verification: every config master accepts runs the same services after migration or carries a behavior_change note, and the rejected combinations are exactly master's. Each new rule was also broken on purpose and caught by these tests.
  • Every combination of node-mode × bootnode-mode × RPC × full-node × swap/chequebook/incentives (864 configs) resolved through the bound flags and compared across revisions; configs without node-mode resolve as on master; the only legacy change across the follow-up fixes is that swap without a chain is rejected before the node is built.
  • make lint (0 issues), go vet ./..., make build and make test pass.
  • The real config pipeline (config file, flat and nested blockchain-rpc keys, BEE_* environment variables, flags) was exercised for the same rules, including empty, null, mixed-case and whitespace values.
  • bee start smoke tests against an unreachable RPC endpoint, legacy and node-mode configs both reaching chain init; invalid configs exit 1 with the error printed; legacy rejections compared against a master build.
  • Beekeeper: the local-dns cluster in fix(config): disable options rejected by bee in light and ultra-light… beekeeper#621 runs a bee-legacy node (node-mode unset, full-node: true) next to node-mode: full nodes. BEEKEEPER_BRANCH is pinned to that branch to exercise both behaviours and is reset to master before merge. Beekeeper master still uses full-node, which this PR handles as legacy; Add support for 'mantaray' manifest, and make it default #621 should merge only after a bee release that knows node-mode, since older bee rejects the unknown key.

AI Disclosure

  • This PR contains code that has been generated by an LLM.
  • I have reviewed the AI generated code thoroughly.
  • I possess the technical expertise to responsibly review the code generated in this PR.

Resolves a conflict in pkg/node/node.go where master (#5460) added a
startup block-height verification guard for the postage listener, while
this branch replaced the o.FullNodeMode bool with o.NodeMode == FullMode.

Resolution keeps master's new guard and applies the NodeMode predicate
to the subsequent batch-service start branch.
Resolved packaging yaml conflicts (bee, scoop, homebrew-amd64,
homebrew-arm64): kept the mode-based section layout from this branch and
added the new 'use-simd-hashing' option from master. The p2p-wss,
nat-wss, autotls, and withdrawal-addresses-whitelist options master
appended already exist in the reorganized Network/Payments sections, so
master's duplicates were dropped.
Resolve conflicts from v2.8.0 (#5477):
- cmd.go: keep node-mode flag and deprecated full-node path while adding
  master's chequebook-verification and chequebook-min-balance flags;
  keep chequebook-enable default of false.
- node.go: take master's chain-enabled chequebook factory init (wallet
  ERC20 resolution); adapt new chequebook-verification check to use
  NodeMode == FullMode instead of the removed FullNodeMode field.
- packaging/*.yaml: keep the section-organized layout and add the new
  chequebook-verification option.
Split resolveNodeMode into a legacy regime (node-mode unset) that
reproduces the behaviour of releases before node-mode existed, and a
mode-owned regime (node-mode set) where the mode supplies defaults for
swap-enable, chequebook-enable and storage-incentives-enable.

Legacy regime: restore the former true defaults of chequebook-enable and
storage-incentives-enable when unset, infer the mode from full-node and
blockchain-rpc-endpoint as before, apply no new validation, and log a
deprecation warning naming the inferred mode and the removal release.
This fixes upgraded configs that were broken in both directions: a light
node with swap-enable only silently lost cheque settlement, and a config
carrying the old chequebook-enable default without swap, or a bootnode
with storage-incentives-enable false, refused to start.

Mode-owned regime: full implies swap, chequebook and incentives unless
explicitly disabled (bootnodes exempt, NewBee never starts them there),
light and full require blockchain-rpc-endpoint, and options a mode
cannot support are rejected only when explicitly enabled. node-mode=full
is now sufficient on its own; validateFullMode and the full-node restore
loop are removed.

The node-mode flag default becomes empty so an unset value selects the
legacy regime without relying on viper's IsSet semantics for pflag
defaults. Tests cover both regimes, including a run against the real
bound start flags.
… defaults

Add BEE_NODE_MODE to the docker-compose environment passthrough and the
env template, mark BEE_FULL_NODE deprecated, and correct the stale true
defaults documented for BEE_CHEQUEBOOK_ENABLE and
BEE_STORAGE_INCENTIVES_ENABLE. Document in the bee.yaml variants that
full mode implies the swap, chequebook and incentives options and that
an unset node-mode falls back to deprecated inference.
@martinconic martinconic changed the title Refactor/node mode two regimes refactor: node mode two regimes Sep 3, 2026
@martinconic
martinconic marked this pull request as ready for review September 3, 2026 13:02

@sbackend123 sbackend123 left a comment

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.

LGTM

Comment thread cmd/bee/cmd/start.go Outdated
}
}
modeStr := c.config.GetString(optionNameNodeMode)
if modeStr == "" {

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.

Seems, this differs from the documented distinction based on whether node-mode is set. Should we use IsSet to distinguish an omitted value from an explicitly empty one?

Comment thread packaging/docker/env Outdated
# BEE_CACHE_RETRIEVAL=true
## enable chequebook (default true)
# BEE_CHEQUEBOOK_ENABLE=true
## enable chequebook; requires swap; implied by BEE_NODE_MODE=full (default false)

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.

When BEE_NODE_MODE is unset, the legacy resolver restores chequebook-enable to true. Could we clarify that false applies only when BEE_NODE_MODE is explicitly set?

@martinconic
martinconic changed the base branch from refactor/node-mode-config to master September 4, 2026 12:11

@gacevicljubisa gacevicljubisa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should have backwards comaptibile bahviour, and only if new flag is explicitly set, then we may change it.

Comment thread cmd/bee/cmd/cmd.go Outdated
Comment thread cmd/bee/cmd/cmd.go Outdated
…two-regimes

# Conflicts:
#	packaging/bee.yaml
#	packaging/homebrew-amd64/bee.yaml
#	packaging/homebrew-arm64/bee.yaml
#	packaging/scoop/bee.yaml
Use the external test package, as the other tests in cmd/bee/cmd do, and
expose the option names and resolver through export_test.go.
A full bootnode does not get swap implied, but chequebook-enable kept its
flag default of true, so every full bootnode in the node-mode regime was
rejected with "chequebook-enable requires swap-enable to be true". Set
chequebook-enable and storage-incentives-enable to false when unset, as
the light and ultra-light modes do, and cover the case with bound flags.
Resolve the node mode in the start command before the node is built in
the background. A rejected configuration is now returned from RunE, so
bee prints the error and exits with a non-zero status instead of logging
it and exiting 0, and it is rejected before the keystore is unlocked.
An unset or unknown NodeMode enabled the chain backend while the API
reported the node as ultra-light.
The exclusion matches nothing; golangci-lint reports no issues without it.
The examples showed ultra-light, but the default is unset, which infers
the mode from the legacy options.
2b8338d forced chequebook-enable to false on every full bootnode where
it was unset, so a bootnode with swap explicitly enabled ran swap without
a chequebook. Replace the per-mode overrides with one rule after the mode
switch: when chequebook-enable is not set explicitly it follows
swap-enable. Outcomes through the bound start flags are otherwise
unchanged, and bare-config resolution now agrees with them.

Ultra-light now rejects an explicit chequebook-enable with a mode-specific
error instead of pointing at swap-enable, and warns when a
blockchain-rpc-endpoint is configured but ignored.

Correct the bootnode comments, which claimed NewBee never starts swap
there, and document the exemption in the packaged configs.
The deprecation warning told legacy operators to add node-mode, but doing
only that changed what half of the legacy configs run: a full node without
swap gained swap and a chequebook, and configs carrying the old defaults
explicitly were rejected. The warning now lists the options to set and to
remove so the node keeps its behavior, and notes where that is not
possible (chequebook-verification without swap). A test applies the
listed migration to every legacy combination of the mode options.

Mode-dependent checks (bootnode must be full, reserve-capacity-doubling
only on full nodes, swap needs a chain, chequebook-verification needs a
full node with chequebook and chain) run in both regimes before the node
is built, so they exit non-zero before the keystore is unlocked. Legacy
configs are rejected exactly as before node-mode existed.

With node-mode set, the value is matched case-insensitively and the
blockchain-rpc-endpoint is trimmed, and a bootnode warns that
storage-incentives-enable has no effect. The resolver tests run on the
real start flags, so the legacy default-restoring loop is gone.
node-mode: light keeps swap opt-in, as light nodes had before node-mode.
Without swap the node pays peers only through the free pseudosettle
allowance, so it now logs that uploads and downloads are limited to it
and how to exceed it. The packaged configs say the same.
An explicit swap-enable, chequebook-enable or storage-incentives-enable
false on a full node is a deliberate choice, and the legacy migration
suggests swap-enable=false itself, so logging it as a warning on every
start gave operators a warning they could not resolve. Report it at Info,
which stays visible at the default verbosity.
Beekeeper master configures nodes with full-node, which bee resolves
through the legacy node mode. The beekeeper node-mode profiles merge
after a bee release that knows node-mode.
@martinconic
martinconic force-pushed the refactor/node-mode-two-regimes branch from 278f89d to e289384 Compare October 16, 2026 15:14
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.

Investigate and refactor Bee node modes in node.go

4 participants