From e386677459c1509017d711031a0606182057c312 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 9 Apr 2026 12:56:27 +0300 Subject: [PATCH 01/11] refactor: replace full-node flag with explicit node-mode config --- cmd/bee/cmd/cmd.go | 13 +- cmd/bee/cmd/start.go | 53 +++++++- packaging/bee.yaml | 216 +++++++++++++++++------------- packaging/homebrew-amd64/bee.yaml | 216 +++++++++++++++++------------- packaging/homebrew-arm64/bee.yaml | 216 +++++++++++++++++------------- packaging/scoop/bee.yaml | 216 +++++++++++++++++------------- pkg/node/node.go | 70 ++++++---- 7 files changed, 580 insertions(+), 420 deletions(-) diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index 37542a7bd38..8eb9a631a52 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -52,9 +52,10 @@ const ( optionNameBootnodeMode = "bootnode-mode" optionNameSwapFactoryAddress = "swap-factory-address" optionNameSwapInitialDeposit = "swap-initial-deposit" + optionNameNodeMode = "node-mode" optionNameSwapEnable = "swap-enable" optionNameChequebookEnable = "chequebook-enable" - optionNameFullNode = "full-node" + optionNameFullNode = "full-node" // Deprecated: use node-mode instead. optionNamePostageContractAddress = "postage-stamp-address" optionNamePostageContractStartBlock = "postage-stamp-start-block" optionNamePriceOracleAddress = "price-oracle-address" @@ -304,9 +305,13 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Duration(optionNameBlockchainRpcKeepalive, 30*time.Second, "blockchain rpc TCP keepalive interval") cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory addresses") cmd.Flags().String(optionNameSwapInitialDeposit, "0", "initial deposit if deploying a new chequebook") + cmd.Flags().String(optionNameNodeMode, string(node.UltraLightMode), "node operational mode: full, light, or ultra-light") cmd.Flags().Bool(optionNameSwapEnable, false, "enable swap") - cmd.Flags().Bool(optionNameChequebookEnable, true, "enable chequebook") - cmd.Flags().Bool(optionNameFullNode, false, "cause the node to start in full mode") + cmd.Flags().Bool(optionNameChequebookEnable, false, "enable chequebook (requires swap-enable)") + cmd.Flags().Bool(optionNameFullNode, false, "cause the node to start in full mode (deprecated: use --node-mode=full)") + if err := cmd.Flags().MarkDeprecated(optionNameFullNode, "use --node-mode=full instead"); err != nil { + panic(err) + } cmd.Flags().String(optionNamePostageContractAddress, "", "postage stamp contract address") cmd.Flags().Uint64(optionNamePostageContractStartBlock, 0, "postage stamp contract start block number") cmd.Flags().String(optionNamePriceOracleAddress, "", "price oracle contract address") @@ -322,7 +327,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Bool(optionNamePProfMutex, false, "enable pprof mutex profile") cmd.Flags().StringSlice(optionNameStaticNodes, []string{}, "protect nodes from getting kicked out on bootnode") cmd.Flags().Bool(optionNameAllowPrivateCIDRs, false, "allow to advertise private CIDRs to the public network") - cmd.Flags().Bool(optionNameStorageIncentivesEnable, true, "enable storage incentives feature") + cmd.Flags().Bool(optionNameStorageIncentivesEnable, false, "enable storage incentives feature (full node only)") cmd.Flags().Uint64(optionNameStateStoreCacheCapacity, 100_000, "lru memory caching capacity in number of statestore entries") cmd.Flags().String(optionNameTargetNeighborhood, "", "neighborhood to target in binary format (ex: 111111001) for mining the initial overlay") cmd.Flags().String(optionNameNeighborhoodSuggester, "https://api.swarmscan.io/v1/network/neighborhoods/suggestion", "suggester for target neighborhood") diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 5773c4af3e3..9dc382d8f93 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -204,9 +204,13 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo } bootNode := c.config.GetBool(optionNameBootnodeMode) - fullNode := c.config.GetBool(optionNameFullNode) - if bootNode && !fullNode { + nodeMode, err := c.resolveNodeMode(logger) + if err != nil { + return nil, err + } + + if bootNode && nodeMode != node.FullMode { return nil, errors.New("boot node must be started as a full node") } @@ -297,7 +301,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo EnableWS: c.config.GetBool(optionNameP2PWSEnable), AutoTLSDomain: c.config.GetString(optionAutoTLSDomain), AutoTLSRegistrationEndpoint: c.config.GetString(optionAutoTLSRegistrationEndpoint), - FullNodeMode: fullNode, + NodeMode: nodeMode, Logger: logger, MinimumGasTipCap: c.config.GetUint64(optionNameMinimumGasTipCap), GasLimitFallback: c.config.GetUint64(optionNameGasLimitFallback), @@ -337,6 +341,49 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo return b, err } +// resolveNodeMode determines the effective node mode from config. +// --node-mode takes precedence; the deprecated --full-node flag is honoured as a fallback. +// It also validates that the required options for each mode are present. +func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { + rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) + swapEnable := c.config.GetBool(optionNameSwapEnable) + + // Resolve the mode: explicit node-mode wins, then legacy full-node, then default ultra-light. + var mode node.NodeMode + if c.config.IsSet(optionNameNodeMode) { + mode = node.NodeMode(c.config.GetString(optionNameNodeMode)) + if !mode.IsValid() { + return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", mode) + } + } else if c.config.GetBool(optionNameFullNode) { + logger.Warning("--full-node is deprecated, use --node-mode=full instead") + mode = node.FullMode + } else { + mode = node.UltraLightMode + } + + // Validate mode-specific requirements. + switch mode { + case node.FullMode: + if rpcEndpoint == "" { + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") + } + if !swapEnable { + return "", errors.New("full node requires swap-enable to be true") + } + case node.LightMode: + if rpcEndpoint == "" { + return "", errors.New("light node requires blockchain-rpc-endpoint to be set") + } + case node.UltraLightMode: + if swapEnable { + return "", errors.New("ultra-light node cannot have swap-enable set to true") + } + } + + return mode, nil +} + type program struct { start func() stop func() diff --git a/packaging/bee.yaml b/packaging/bee.yaml index ef04fb716c6..d90ce63c2a1 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -1,13 +1,13 @@ ## Bee configuration - https://docs.ethswarm.org/docs/working-with-bee/configuration -## allow to advertise private CIDRs to the public network -# allow-private-cidrs: false -## HTTP API listen address -# api-addr: 127.0.0.1:1633 -## chain block time -# block-time: "5" -## block number cache sync interval in blocks -# block-sync-interval: 10 +## ── Node mode ──────────────────────────────────────────────────────────────── +## Selects the operational mode of this node. +## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## light - uploads and downloads only; requires blockchain-rpc +## ultra-light - free-tier downloads only; no blockchain connection needed (default) +# node-mode: ultra-light + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,22 +15,96 @@ # tls-timeout: 10s # idle-timeout: 90s # keepalive: 30s +## chain block time +# block-time: "5" +## block number cache sync interval in blocks +# block-sync-interval: 10 + +## ── Swap / chequebook (full and light nodes only) ──────────────────────────── +## enable swap +# swap-enable: false +## enable chequebook +# chequebook-enable: false +## swap factory addresses +# swap-factory-address: "" +## initial deposit if deploying a new chequebook +# swap-initial-deposit: "0" + +## ── Full node only ──────────────────────────────────────────────────────────── +## enable storage incentives feature +# storage-incentives-enable: false +## reserve capacity doubling +# reserve-capacity-doubling: 0 +## minimum radius storage threshold +# minimum-storage-radius: "0" +## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay +# target-neighborhood: "" +## suggester for target neighborhood +# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion +## redistribution contract address +# redistribution-address: "" +## staking contract address +# staking-address: "" + +## ── Network ─────────────────────────────────────────────────────────────────── +## triggers connect to main net bootnodes +# mainnet: true +## ID of the Swarm network +# network-id: "1" ## initial nodes to connect to # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false -## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes -# cache-capacity: "1000000" -## enable forwarded content caching -# cache-retrieval: true -## enable chequebook -# chequebook-enable: true -## config file (default is $HOME/.bee.yaml) -config: "/etc/bee/bee.yaml" +## protect nodes from getting kicked out on bootnode +# static-nodes: [] +## P2P listen address +# p2p-addr: :1634 +## enable P2P WebSocket transport +# p2p-ws-enable: false +## enable wss p2p connections +# p2p-wss-enable: false +## wss address +# p2p-wss-addr: :1635 +## NAT exposed address +# nat-addr: "" +## WSS NAT exposed address +# nat-wss-addr: "" +## autotls domain +# autotls-domain: "" +## autotls registration endpoint +# autotls-registration-endpoint: "" +## autotls ca endpoint +# autotls-ca-endpoint: "" +## allow to advertise private CIDRs to the public network +# allow-private-cidrs: false + +## ── HTTP API ────────────────────────────────────────────────────────────────── +## HTTP API listen address +# api-addr: 127.0.0.1:1633 ## origins with CORS headers enabled # cors-allowed-origins: [] + +## ── Storage ─────────────────────────────────────────────────────────────────── ## data directory data-dir: "/var/lib/bee" +## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes +# cache-capacity: "1000000" +## enable forwarded content caching +# cache-retrieval: true +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## skip postage snapshot +# skip-postage-snapshot: false +## forces the node to resync postage contract data +# resync: false +## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url +# resolver-options: [] +## price oracle contract address +# price-oracle-address: "" + +## ── Database ────────────────────────────────────────────────────────────────── ## size of block cache of the database in bytes # db-block-cache-capacity: "33554432" ## disables db compactions triggered by seeks @@ -39,72 +113,36 @@ data-dir: "/var/lib/bee" # db-open-files-limit: "200" ## size of the database write buffer in bytes # db-write-buffer-size: "33554432" -## cause the node to start in full mode -# full-node: false -## help for printconfig -# help: false -## triggers connect to main net bootnodes. -# mainnet: true +## lru memory caching capacity in number of statestore entries +# statestore-cache-capacity: "100000" + +## ── Payments ────────────────────────────────────────────────────────────────── +## threshold in BZZ where you expect to get paid from your peers +# payment-threshold: "13500000" +## percentage below the peers payment threshold when we initiate settlement +# payment-early-percent: 50 +## excess debt above payment threshold in percentages where you disconnect from your peer +# payment-tolerance-percent: 25 ## minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap # minimum-gas-tip-cap: 0 -## minimum radius storage threshold -# minimum-storage-radius: "0" -## NAT exposed address -# nat-addr: "" -## suggester for target neighborhood -# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion -## ID of the Swarm network -# network-id: "1" -## P2P listen address -# p2p-addr: :1634 -## enable P2P WebSocket transport -# p2p-ws-enable: false +## gas limit fallback when estimation fails for contract transactions (default 500000) +# gas-limit-fallback: 500000 +## skips the gas estimate step for contract transactions +# transaction-debug-mode: false +## withdrawal target addresses +# withdrawal-addresses-whitelist: [] + +## ── Keys / identity ─────────────────────────────────────────────────────────── +## config file (default is $HOME/.bee.yaml) +config: "/etc/bee/bee.yaml" ## password for decrypting keys # password: "" ## path to a file that contains password for decrypting keys password-file: "/var/lib/bee/password" -## percentage below the peers payment threshold when we initiate settlement -# payment-early-percent: 50 -## threshold in BZZ where you expect to get paid from your peers -# payment-threshold: "13500000" -## excess debt above payment threshold in percentages where you disconnect from your peer -# payment-tolerance-percent: 25 -## postage stamp contract address -# postage-stamp-address: "" -## postage stamp contract start block number -# postage-stamp-start-block: "0" -## enable pprof mutex profile -# pprof-mutex: false -## enable pprof block profile -# pprof-profile: false -## price oracle contract address -# price-oracle-address: "" -## redistribution contract address -# redistribution-address: "" -## reserve capacity doubling -# reserve-capacity-doubling: 0 -## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url -# resolver-options: [] -## forces the node to resync postage contract data -# resync: false -## skip postage snapshot -# skip-postage-snapshot: false -## staking contract address -# staking-address: "" -## lru memory caching capacity in number of statestore entries -# statestore-cache-capacity: "100000" -## protect nodes from getting kicked out on bootnode -# static-nodes: [] -## enable storage incentives feature -# storage-incentives-enable: true -## enable swap -# swap-enable: false -## swap factory addresses -# swap-factory-address: "" -## initial deposit if deploying a new chequebook -# swap-initial-deposit: "0" -## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay -# target-neighborhood: "" + +## ── Logging / tracing ───────────────────────────────────────────────────────── +## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace +# verbosity: info ## enable tracing # tracing-enable: false ## endpoint to send tracing data @@ -115,27 +153,13 @@ password-file: "/var/lib/bee/password" # tracing-port: "" ## service name identifier for tracing # tracing-service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 500000) -# gas-limit-fallback: 500000 -## skips the gas estimate step for contract transactions -# transaction-debug-mode: false -## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace -# verbosity: info +## enable pprof mutex profile +# pprof-mutex: false +## enable pprof block profile +# pprof-profile: false + +## ── Miscellaneous ───────────────────────────────────────────────────────────── ## maximum node warmup duration; proceeds when stable or after this time # warmup-time: 5m0s ## send a welcome message string during handshakes # welcome-message: "" -## withdrawal target addresses -# withdrawal-addresses-whitelist: [] -## enable wss p2p connections (default: false) -# p2p-wss-enable: false -## wss address (default: :1635) -# p2p-wss-addr: :1635 -## WSS NAT exposed address -# nat-wss-addr: "" -## autotls domain (default: libp2p.direct) -# autotls-domain: "" -## autotls registration endpoint (default: https://registration.libp2p.direct) -# autotls-registration-endpoint: "" -## autotls ca endpoint (default: https://acme-v02.api.letsencrypt.org/directory) -# autotls-ca-endpoint: "" diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index 2aac12016fe..1258eee70b8 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -1,13 +1,13 @@ ## Bee configuration - https://docs.ethswarm.org/docs/working-with-bee/configuration -## allow to advertise private CIDRs to the public network -# allow-private-cidrs: false -## HTTP API listen address -# api-addr: 127.0.0.1:1633 -## chain block time -# block-time: "5" -## block number cache sync interval in blocks -# block-sync-interval: 10 +## ── Node mode ──────────────────────────────────────────────────────────────── +## Selects the operational mode of this node. +## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## light - uploads and downloads only; requires blockchain-rpc +## ultra-light - free-tier downloads only; no blockchain connection needed (default) +# node-mode: ultra-light + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,22 +15,96 @@ # tls-timeout: 10s # idle-timeout: 90s # keepalive: 30s +## chain block time +# block-time: "5" +## block number cache sync interval in blocks +# block-sync-interval: 10 + +## ── Swap / chequebook (full and light nodes only) ──────────────────────────── +## enable swap +# swap-enable: false +## enable chequebook +# chequebook-enable: false +## swap factory addresses +# swap-factory-address: "" +## initial deposit if deploying a new chequebook +# swap-initial-deposit: "0" + +## ── Full node only ──────────────────────────────────────────────────────────── +## enable storage incentives feature +# storage-incentives-enable: false +## reserve capacity doubling +# reserve-capacity-doubling: 0 +## minimum radius storage threshold +# minimum-storage-radius: "0" +## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay +# target-neighborhood: "" +## suggester for target neighborhood +# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion +## redistribution contract address +# redistribution-address: "" +## staking contract address +# staking-address: "" + +## ── Network ─────────────────────────────────────────────────────────────────── +## triggers connect to main net bootnodes +# mainnet: true +## ID of the Swarm network +# network-id: "1" ## initial nodes to connect to # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false -## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes -# cache-capacity: "1000000" -## enable forwarded content caching -# cache-retrieval: true -## enable chequebook -# chequebook-enable: true -## config file (default is $HOME/.bee.yaml) -config: "/usr/local/etc/swarm-bee/bee.yaml" +## protect nodes from getting kicked out on bootnode +# static-nodes: [] +## P2P listen address +# p2p-addr: :1634 +## enable P2P WebSocket transport +# p2p-ws-enable: false +## enable wss p2p connections +# p2p-wss-enable: false +## wss address +# p2p-wss-addr: :1635 +## NAT exposed address +# nat-addr: "" +## WSS NAT exposed address +# nat-wss-addr: "" +## autotls domain +# autotls-domain: "" +## autotls registration endpoint +# autotls-registration-endpoint: "" +## autotls ca endpoint +# autotls-ca-endpoint: "" +## allow to advertise private CIDRs to the public network +# allow-private-cidrs: false + +## ── HTTP API ────────────────────────────────────────────────────────────────── +## HTTP API listen address +# api-addr: 127.0.0.1:1633 ## origins with CORS headers enabled # cors-allowed-origins: [] + +## ── Storage ─────────────────────────────────────────────────────────────────── ## data directory data-dir: "/usr/local/var/lib/swarm-bee" +## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes +# cache-capacity: "1000000" +## enable forwarded content caching +# cache-retrieval: true +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## skip postage snapshot +# skip-postage-snapshot: false +## forces the node to resync postage contract data +# resync: false +## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url +# resolver-options: [] +## price oracle contract address +# price-oracle-address: "" + +## ── Database ────────────────────────────────────────────────────────────────── ## size of block cache of the database in bytes # db-block-cache-capacity: "33554432" ## disables db compactions triggered by seeks @@ -39,72 +113,36 @@ data-dir: "/usr/local/var/lib/swarm-bee" # db-open-files-limit: "200" ## size of the database write buffer in bytes # db-write-buffer-size: "33554432" -## cause the node to start in full mode -# full-node: false -## help for printconfig -# help: false -## triggers connect to main net bootnodes. -# mainnet: true +## lru memory caching capacity in number of statestore entries +# statestore-cache-capacity: "100000" + +## ── Payments ────────────────────────────────────────────────────────────────── +## threshold in BZZ where you expect to get paid from your peers +# payment-threshold: "13500000" +## percentage below the peers payment threshold when we initiate settlement +# payment-early-percent: 50 +## excess debt above payment threshold in percentages where you disconnect from your peer +# payment-tolerance-percent: 25 ## minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap # minimum-gas-tip-cap: 0 -## minimum radius storage threshold -# minimum-storage-radius: "0" -## NAT exposed address -# nat-addr: "" -## suggester for target neighborhood -# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion -## ID of the Swarm network -# network-id: "1" -## P2P listen address -# p2p-addr: :1634 -## enable P2P WebSocket transport -# p2p-ws-enable: false +## gas limit fallback when estimation fails for contract transactions (default 500000) +# gas-limit-fallback: 500000 +## skips the gas estimate step for contract transactions +# transaction-debug-mode: false +## withdrawal target addresses +# withdrawal-addresses-whitelist: [] + +## ── Keys / identity ─────────────────────────────────────────────────────────── +## config file (default is $HOME/.bee.yaml) +config: "/usr/local/etc/swarm-bee/bee.yaml" ## password for decrypting keys # password: "" ## path to a file that contains password for decrypting keys password-file: "/usr/local/var/lib/swarm-bee/password" -## percentage below the peers payment threshold when we initiate settlement -# payment-early-percent: 50 -## threshold in BZZ where you expect to get paid from your peers -# payment-threshold: "13500000" -## excess debt above payment threshold in percentages where you disconnect from your peer -# payment-tolerance-percent: 25 -## postage stamp contract address -# postage-stamp-address: "" -## postage stamp contract start block number -# postage-stamp-start-block: "0" -## enable pprof mutex profile -# pprof-mutex: false -## enable pprof block profile -# pprof-profile: false -## price oracle contract address -# price-oracle-address: "" -## redistribution contract address -# redistribution-address: "" -## reserve capacity doubling -# reserve-capacity-doubling: 0 -## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url -# resolver-options: [] -## forces the node to resync postage contract data -# resync: false -## skip postage snapshot -# skip-postage-snapshot: false -## staking contract address -# staking-address: "" -## lru memory caching capacity in number of statestore entries -# statestore-cache-capacity: "100000" -## protect nodes from getting kicked out on bootnode -# static-nodes: [] -## enable storage incentives feature -# storage-incentives-enable: true -## enable swap -# swap-enable: false -## swap factory addresses -# swap-factory-address: "" -## initial deposit if deploying a new chequebook -# swap-initial-deposit: "0" -## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay -# target-neighborhood: "" + +## ── Logging / tracing ───────────────────────────────────────────────────────── +## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace +# verbosity: info ## enable tracing # tracing-enable: false ## endpoint to send tracing data @@ -115,27 +153,13 @@ password-file: "/usr/local/var/lib/swarm-bee/password" # tracing-port: "" ## service name identifier for tracing # tracing-service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 500000) -# gas-limit-fallback: 500000 -## skips the gas estimate step for contract transactions -# transaction-debug-mode: false -## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace -# verbosity: info +## enable pprof mutex profile +# pprof-mutex: false +## enable pprof block profile +# pprof-profile: false + +## ── Miscellaneous ───────────────────────────────────────────────────────────── ## maximum node warmup duration; proceeds when stable or after this time # warmup-time: 5m0s ## send a welcome message string during handshakes # welcome-message: "" -## withdrawal target addresses -# withdrawal-addresses-whitelist: [] -## enable wss p2p connections (default: false) -# p2p-wss-enable: false -## wss address (default: :1635) -# p2p-wss-addr: :1635 -## WSS NAT exposed address -# nat-wss-addr: "" -## autotls domain (default: libp2p.direct) -# autotls-domain: "" -## autotls registration endpoint (default: https://registration.libp2p.direct) -# autotls-registration-endpoint: "" -## autotls ca endpoint (default: https://acme-v02.api.letsencrypt.org/directory) -# autotls-ca-endpoint: "" diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index f812f395f07..5734d6eb924 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -1,13 +1,13 @@ ## Bee configuration - https://docs.ethswarm.org/docs/working-with-bee/configuration -## allow to advertise private CIDRs to the public network -# allow-private-cidrs: false -## HTTP API listen address -# api-addr: 127.0.0.1:1633 -## chain block time -# block-time: "5" -## block number cache sync interval in blocks -# block-sync-interval: 10 +## ── Node mode ──────────────────────────────────────────────────────────────── +## Selects the operational mode of this node. +## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## light - uploads and downloads only; requires blockchain-rpc +## ultra-light - free-tier downloads only; no blockchain connection needed (default) +# node-mode: ultra-light + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,22 +15,96 @@ # tls-timeout: 10s # idle-timeout: 90s # keepalive: 30s +## chain block time +# block-time: "5" +## block number cache sync interval in blocks +# block-sync-interval: 10 + +## ── Swap / chequebook (full and light nodes only) ──────────────────────────── +## enable swap +# swap-enable: false +## enable chequebook +# chequebook-enable: false +## swap factory addresses +# swap-factory-address: "" +## initial deposit if deploying a new chequebook +# swap-initial-deposit: "0" + +## ── Full node only ──────────────────────────────────────────────────────────── +## enable storage incentives feature +# storage-incentives-enable: false +## reserve capacity doubling +# reserve-capacity-doubling: 0 +## minimum radius storage threshold +# minimum-storage-radius: "0" +## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay +# target-neighborhood: "" +## suggester for target neighborhood +# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion +## redistribution contract address +# redistribution-address: "" +## staking contract address +# staking-address: "" + +## ── Network ─────────────────────────────────────────────────────────────────── +## triggers connect to main net bootnodes +# mainnet: true +## ID of the Swarm network +# network-id: "1" ## initial nodes to connect to # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false -## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes -# cache-capacity: "1000000" -## enable forwarded content caching -# cache-retrieval: true -## enable chequebook -# chequebook-enable: true -## config file (default is $HOME/.bee.yaml) -config: "/opt/homebrew/etc/swarm-bee/bee.yaml" +## protect nodes from getting kicked out on bootnode +# static-nodes: [] +## P2P listen address +# p2p-addr: :1634 +## enable P2P WebSocket transport +# p2p-ws-enable: false +## enable wss p2p connections +# p2p-wss-enable: false +## wss address +# p2p-wss-addr: :1635 +## NAT exposed address +# nat-addr: "" +## WSS NAT exposed address +# nat-wss-addr: "" +## autotls domain +# autotls-domain: "" +## autotls registration endpoint +# autotls-registration-endpoint: "" +## autotls ca endpoint +# autotls-ca-endpoint: "" +## allow to advertise private CIDRs to the public network +# allow-private-cidrs: false + +## ── HTTP API ────────────────────────────────────────────────────────────────── +## HTTP API listen address +# api-addr: 127.0.0.1:1633 ## origins with CORS headers enabled # cors-allowed-origins: [] + +## ── Storage ─────────────────────────────────────────────────────────────────── ## data directory data-dir: "/opt/homebrew/var/lib/swarm-bee" +## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes +# cache-capacity: "1000000" +## enable forwarded content caching +# cache-retrieval: true +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## skip postage snapshot +# skip-postage-snapshot: false +## forces the node to resync postage contract data +# resync: false +## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url +# resolver-options: [] +## price oracle contract address +# price-oracle-address: "" + +## ── Database ────────────────────────────────────────────────────────────────── ## size of block cache of the database in bytes # db-block-cache-capacity: "33554432" ## disables db compactions triggered by seeks @@ -39,72 +113,36 @@ data-dir: "/opt/homebrew/var/lib/swarm-bee" # db-open-files-limit: "200" ## size of the database write buffer in bytes # db-write-buffer-size: "33554432" -## cause the node to start in full mode -# full-node: false -## help for printconfig -# help: false -## triggers connect to main net bootnodes. -# mainnet: true +## lru memory caching capacity in number of statestore entries +# statestore-cache-capacity: "100000" + +## ── Payments ────────────────────────────────────────────────────────────────── +## threshold in BZZ where you expect to get paid from your peers +# payment-threshold: "13500000" +## percentage below the peers payment threshold when we initiate settlement +# payment-early-percent: 50 +## excess debt above payment threshold in percentages where you disconnect from your peer +# payment-tolerance-percent: 25 ## minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap # minimum-gas-tip-cap: 0 -## minimum radius storage threshold -# minimum-storage-radius: "0" -## NAT exposed address -# nat-addr: "" -## suggester for target neighborhood -# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion -## ID of the Swarm network -# network-id: "1" -## P2P listen address -# p2p-addr: :1634 -## enable P2P WebSocket transport -# p2p-ws-enable: false +## gas limit fallback when estimation fails for contract transactions (default 500000) +# gas-limit-fallback: 500000 +## skips the gas estimate step for contract transactions +# transaction-debug-mode: false +## withdrawal target addresses +# withdrawal-addresses-whitelist: [] + +## ── Keys / identity ─────────────────────────────────────────────────────────── +## config file (default is $HOME/.bee.yaml) +config: "/opt/homebrew/etc/swarm-bee/bee.yaml" ## password for decrypting keys # password: "" ## path to a file that contains password for decrypting keys password-file: "/opt/homebrew/var/lib/swarm-bee/password" -## percentage below the peers payment threshold when we initiate settlement -# payment-early-percent: 50 -## threshold in BZZ where you expect to get paid from your peers -# payment-threshold: "13500000" -## excess debt above payment threshold in percentages where you disconnect from your peer -# payment-tolerance-percent: 25 -## postage stamp contract address -# postage-stamp-address: "" -## postage stamp contract start block number -# postage-stamp-start-block: "0" -## enable pprof mutex profile -# pprof-mutex: false -## enable pprof block profile -# pprof-profile: false -## price oracle contract address -# price-oracle-address: "" -## redistribution contract address -# redistribution-address: "" -## reserve capacity doubling -# reserve-capacity-doubling: 0 -## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url -# resolver-options: [] -## forces the node to resync postage contract data -# resync: false -## skip postage snapshot -# skip-postage-snapshot: false -## staking contract address -# staking-address: "" -## lru memory caching capacity in number of statestore entries -# statestore-cache-capacity: "100000" -## protect nodes from getting kicked out on bootnode -# static-nodes: [] -## enable storage incentives feature -# storage-incentives-enable: true -## enable swap -# swap-enable: false -## swap factory addresses -# swap-factory-address: "" -## initial deposit if deploying a new chequebook -# swap-initial-deposit: "0" -## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay -# target-neighborhood: "" + +## ── Logging / tracing ───────────────────────────────────────────────────────── +## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace +# verbosity: info ## enable tracing # tracing-enable: false ## endpoint to send tracing data @@ -115,27 +153,13 @@ password-file: "/opt/homebrew/var/lib/swarm-bee/password" # tracing-port: "" ## service name identifier for tracing # tracing-service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 500000) -# gas-limit-fallback: 500000 -## skips the gas estimate step for contract transactions -# transaction-debug-mode: false -## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace -# verbosity: info +## enable pprof mutex profile +# pprof-mutex: false +## enable pprof block profile +# pprof-profile: false + +## ── Miscellaneous ───────────────────────────────────────────────────────────── ## maximum node warmup duration; proceeds when stable or after this time # warmup-time: 5m0s ## send a welcome message string during handshakes # welcome-message: "" -## withdrawal target addresses -# withdrawal-addresses-whitelist: [] -## enable wss p2p connections (default: false) -# p2p-wss-enable: false -## wss address (default: :1635) -# p2p-wss-addr: :1635 -## WSS NAT exposed address -# nat-wss-addr: "" -## autotls domain (default: libp2p.direct) -# autotls-domain: "" -## autotls registration endpoint (default: https://registration.libp2p.direct) -# autotls-registration-endpoint: "" -## autotls ca endpoint (default: https://acme-v02.api.letsencrypt.org/directory) -# autotls-ca-endpoint: "" diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index f44aaf0e0d0..620245831b8 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -1,13 +1,13 @@ ## Bee configuration - https://docs.ethswarm.org/docs/working-with-bee/configuration -## allow to advertise private CIDRs to the public network -# allow-private-cidrs: false -## HTTP API listen address -# api-addr: 127.0.0.1:1633 -## chain block time -# block-time: "5" -## block number cache sync interval in blocks -# block-sync-interval: 10 +## ── Node mode ──────────────────────────────────────────────────────────────── +## Selects the operational mode of this node. +## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## light - uploads and downloads only; requires blockchain-rpc +## ultra-light - free-tier downloads only; no blockchain connection needed (default) +# node-mode: ultra-light + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,22 +15,96 @@ # tls-timeout: 10s # idle-timeout: 90s # keepalive: 30s +## chain block time +# block-time: "5" +## block number cache sync interval in blocks +# block-sync-interval: 10 + +## ── Swap / chequebook (full and light nodes only) ──────────────────────────── +## enable swap +# swap-enable: false +## enable chequebook +# chequebook-enable: false +## swap factory addresses +# swap-factory-address: "" +## initial deposit if deploying a new chequebook +# swap-initial-deposit: "0" + +## ── Full node only ──────────────────────────────────────────────────────────── +## enable storage incentives feature +# storage-incentives-enable: false +## reserve capacity doubling +# reserve-capacity-doubling: 0 +## minimum radius storage threshold +# minimum-storage-radius: "0" +## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay +# target-neighborhood: "" +## suggester for target neighborhood +# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion +## redistribution contract address +# redistribution-address: "" +## staking contract address +# staking-address: "" + +## ── Network ─────────────────────────────────────────────────────────────────── +## triggers connect to main net bootnodes +# mainnet: true +## ID of the Swarm network +# network-id: "1" ## initial nodes to connect to # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false -## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes -# cache-capacity: "1000000" -## enable forwarded content caching -# cache-retrieval: true -## enable chequebook -# chequebook-enable: true -## config file (default is $HOME/.bee.yaml) -config: "./bee.yaml" +## protect nodes from getting kicked out on bootnode +# static-nodes: [] +## P2P listen address +# p2p-addr: :1634 +## enable P2P WebSocket transport +# p2p-ws-enable: false +## enable wss p2p connections +# p2p-wss-enable: false +## wss address +# p2p-wss-addr: :1635 +## NAT exposed address +# nat-addr: "" +## WSS NAT exposed address +# nat-wss-addr: "" +## autotls domain +# autotls-domain: "" +## autotls registration endpoint +# autotls-registration-endpoint: "" +## autotls ca endpoint +# autotls-ca-endpoint: "" +## allow to advertise private CIDRs to the public network +# allow-private-cidrs: false + +## ── HTTP API ────────────────────────────────────────────────────────────────── +## HTTP API listen address +# api-addr: 127.0.0.1:1633 ## origins with CORS headers enabled # cors-allowed-origins: [] + +## ── Storage ─────────────────────────────────────────────────────────────────── ## data directory data-dir: "./data" +## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes +# cache-capacity: "1000000" +## enable forwarded content caching +# cache-retrieval: true +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## skip postage snapshot +# skip-postage-snapshot: false +## forces the node to resync postage contract data +# resync: false +## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url +# resolver-options: [] +## price oracle contract address +# price-oracle-address: "" + +## ── Database ────────────────────────────────────────────────────────────────── ## size of block cache of the database in bytes # db-block-cache-capacity: "33554432" ## disables db compactions triggered by seeks @@ -39,72 +113,36 @@ data-dir: "./data" # db-open-files-limit: "200" ## size of the database write buffer in bytes # db-write-buffer-size: "33554432" -## cause the node to start in full mode -# full-node: false -## help for printconfig -# help: false -## triggers connect to main net bootnodes. -# mainnet: true +## lru memory caching capacity in number of statestore entries +# statestore-cache-capacity: "100000" + +## ── Payments ────────────────────────────────────────────────────────────────── +## threshold in BZZ where you expect to get paid from your peers +# payment-threshold: "13500000" +## percentage below the peers payment threshold when we initiate settlement +# payment-early-percent: 50 +## excess debt above payment threshold in percentages where you disconnect from your peer +# payment-tolerance-percent: 25 ## minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap # minimum-gas-tip-cap: 0 -## minimum radius storage threshold -# minimum-storage-radius: "0" -## NAT exposed address -# nat-addr: "" -## suggester for target neighborhood -# neighborhood-suggester: https://api.swarmscan.io/v1/network/neighborhoods/suggestion -## ID of the Swarm network -# network-id: "1" -## P2P listen address -# p2p-addr: :1634 -## enable P2P WebSocket transport -# p2p-ws-enable: false +## gas limit fallback when estimation fails for contract transactions (default 500000) +# gas-limit-fallback: 500000 +## skips the gas estimate step for contract transactions +# transaction-debug-mode: false +## withdrawal target addresses +# withdrawal-addresses-whitelist: [] + +## ── Keys / identity ─────────────────────────────────────────────────────────── +## config file (default is $HOME/.bee.yaml) +config: "./bee.yaml" ## password for decrypting keys # password: "" ## path to a file that contains password for decrypting keys password-file: "./password" -## percentage below the peers payment threshold when we initiate settlement -# payment-early-percent: 50 -## threshold in BZZ where you expect to get paid from your peers -# payment-threshold: "13500000" -## excess debt above payment threshold in percentages where you disconnect from your peer -# payment-tolerance-percent: 25 -## postage stamp contract address -# postage-stamp-address: "" -## postage stamp contract start block number -# postage-stamp-start-block: "0" -## enable pprof mutex profile -# pprof-mutex: false -## enable pprof block profile -# pprof-profile: false -## price oracle contract address -# price-oracle-address: "" -## redistribution contract address -# redistribution-address: "" -## reserve capacity doubling -# reserve-capacity-doubling: 0 -## ENS compatible API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url -# resolver-options: [] -## forces the node to resync postage contract data -# resync: false -## skip postage snapshot -# skip-postage-snapshot: false -## staking contract address -# staking-address: "" -## lru memory caching capacity in number of statestore entries -# statestore-cache-capacity: "100000" -## protect nodes from getting kicked out on bootnode -# static-nodes: [] -## enable storage incentives feature -# storage-incentives-enable: true -## enable swap -# swap-enable: false -## swap factory addresses -# swap-factory-address: "" -## initial deposit if deploying a new chequebook -# swap-initial-deposit: "0" -## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay -# target-neighborhood: "" + +## ── Logging / tracing ───────────────────────────────────────────────────────── +## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace +# verbosity: info ## enable tracing # tracing-enable: false ## endpoint to send tracing data @@ -115,27 +153,13 @@ password-file: "./password" # tracing-port: "" ## service name identifier for tracing # tracing-service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 500000) -# gas-limit-fallback: 500000 -## skips the gas estimate step for contract transactions -# transaction-debug-mode: false -## log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace -# verbosity: info +## enable pprof mutex profile +# pprof-mutex: false +## enable pprof block profile +# pprof-profile: false + +## ── Miscellaneous ───────────────────────────────────────────────────────────── ## maximum node warmup duration; proceeds when stable or after this time # warmup-time: 5m0s ## send a welcome message string during handshakes # welcome-message: "" -## withdrawal target addresses -# withdrawal-addresses-whitelist: [] -## enable wss p2p connections (default: false) -# p2p-wss-enable: false -## wss address (default: :1635) -# p2p-wss-addr: :1635 -## WSS NAT exposed address -# nat-wss-addr: "" -## autotls domain (default: libp2p.direct) -# autotls-domain: "" -## autotls registration endpoint (default: https://registration.libp2p.direct) -# autotls-registration-endpoint: "" -## autotls ca endpoint (default: https://acme-v02.api.letsencrypt.org/directory) -# autotls-ca-endpoint: "" diff --git a/pkg/node/node.go b/pkg/node/node.go index 0b3be605b53..bd2ff2309a0 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -127,6 +127,23 @@ type Bee struct { ethClientCloser func() } +// NodeMode represents the operational mode of a Bee node as configured by the operator. +type NodeMode string + +const ( + FullMode NodeMode = "full" + LightMode NodeMode = "light" + UltraLightMode NodeMode = "ultra-light" +) + +func (m NodeMode) IsValid() bool { + switch m { + case FullMode, LightMode, UltraLightMode: + return true + } + return false +} + type Options struct { Addr string AllowPrivateCIDRs bool @@ -158,7 +175,7 @@ type Options struct { EnableWS bool AutoTLSDomain string AutoTLSRegistrationEndpoint string - FullNodeMode bool + NodeMode NodeMode GasLimitFallback uint64 Logger log.Logger MinimumGasTipCap uint64 @@ -258,7 +275,7 @@ func NewBee( // light nodes have zero warmup time for pull/pushsync protocols warmupTime := o.WarmupTime - if !o.FullNodeMode { + if o.NodeMode != FullMode { warmupTime = 0 } @@ -284,7 +301,7 @@ func NewBee( } }(b) - if !o.FullNodeMode && o.ReserveCapacityDoubling != 0 { + if o.NodeMode != FullMode && o.ReserveCapacityDoubling != 0 { return nil, fmt.Errorf("reserve capacity doubling is only allowed for full nodes") } @@ -389,7 +406,7 @@ func NewBee( erc20Service erc20.Service ) - chainEnabled := isChainEnabled(o, o.BlockchainRpcEndpoint, logger) + chainEnabled := isChainEnabled(o, logger) var batchStore postage.Storer = new(postage.NoOpBatchStore) var evictFn func([]byte) error @@ -437,10 +454,13 @@ func NewBee( b.transactionCloser = tracerCloser b.transactionMonitorCloser = transactionMonitor - beeNodeMode := api.LightMode - if o.FullNodeMode { + var beeNodeMode api.BeeNodeMode + switch o.NodeMode { + case FullMode: beeNodeMode = api.FullMode - } else if !chainEnabled { + case LightMode: + beeNodeMode = api.LightMode + default: beeNodeMode = api.UltraLightMode } @@ -663,7 +683,7 @@ func NewBee( AutoTLSRegistrationEndpoint: o.AutoTLSRegistrationEndpoint, AutoTLSCAEndpoint: o.AutoTLSCAEndpoint, WelcomeMessage: o.WelcomeMessage, - FullNode: o.FullNodeMode, + FullNode: o.NodeMode == FullMode, Nonce: nonce, ValidateOverlay: chainEnabled, Registry: registry, @@ -789,7 +809,7 @@ func NewBee( MinimumStorageRadius: o.MinimumStorageRadius, } - if o.FullNodeMode && !o.BootnodeMode { + if o.NodeMode == FullMode && !o.BootnodeMode { // configure reserve only for full node lo.ReserveCapacity = reserveCapacity lo.ReserveWakeUpDuration = reserveWakeUpDuration @@ -867,7 +887,7 @@ func NewBee( return nil, errors.New("postage contract is paused") } - if o.FullNodeMode { + if o.NodeMode == FullMode { err = batchSvc.Start(ctx, postageSyncStart) syncStatus.Store(true) if err != nil { @@ -892,7 +912,7 @@ func NewBee( minThreshold := big.NewInt(2 * refreshRate) maxThreshold := big.NewInt(24 * refreshRate) - if !o.FullNodeMode { + if o.NodeMode != FullMode { minThreshold = big.NewInt(2 * lightRefreshRate) } @@ -925,7 +945,7 @@ func NewBee( var enforcedRefreshRate *big.Int - if o.FullNodeMode { + if o.NodeMode == FullMode { enforcedRefreshRate = big.NewInt(refreshRate) } else { enforcedRefreshRate = big.NewInt(lightRefreshRate) @@ -1020,7 +1040,7 @@ func NewBee( if prev == uint32(swarm.MaxBins) { close(initialRadiusC) } - if !o.FullNodeMode { // light and ultra-light nodes do not have a reserve worker to set the radius. + if o.NodeMode != FullMode { // light and ultra-light nodes do not have a reserve worker to set the radius. kad.SetStorageRadius(r) } case <-ctx.Done(): @@ -1047,7 +1067,7 @@ func NewBee( } } - pushSyncProtocol := pushsync.New(swarmAddress, networkID, nonce, p2ps, localStore, waitNetworkRFunc, kad, o.FullNodeMode && !o.BootnodeMode, pssService.TryUnwrap, gsocService.Handle, validStamp, logger, acc, pricer, signer, tracer, detector, uint8(shallowReceiptTolerance)) + pushSyncProtocol := pushsync.New(swarmAddress, networkID, nonce, p2ps, localStore, waitNetworkRFunc, kad, o.NodeMode == FullMode && !o.BootnodeMode, pssService.TryUnwrap, gsocService.Handle, validStamp, logger, acc, pricer, signer, tracer, detector, uint8(shallowReceiptTolerance)) b.pushSyncCloser = pushSyncProtocol // set the pushSyncer in the PSS @@ -1070,7 +1090,7 @@ func NewBee( pushSyncProtocolSpec := pushSyncProtocol.Protocol() pullSyncProtocolSpec := pullSyncProtocol.Protocol() - if o.FullNodeMode && !o.BootnodeMode { + if o.NodeMode == FullMode && !o.BootnodeMode { logger.Info("starting in full mode") } else { if chainEnabled { @@ -1153,7 +1173,7 @@ func NewBee( agent *storageincentives.Agent ) - if o.FullNodeMode && !o.BootnodeMode { + if o.NodeMode == FullMode && !o.BootnodeMode { pullerService = puller.New(swarmAddress, stateStore, kad, localStore, pullSyncProtocol, p2ps, logger, puller.Options{}) b.pullerCloser = pullerService @@ -1473,21 +1493,13 @@ func (b *Bee) Shutdown() error { var ErrShutdownInProgress = errors.New("shutdown in progress") -func isChainEnabled(o *Options, swapEndpoint string, logger log.Logger) bool { - chainDisabled := swapEndpoint == "" - lightMode := !o.FullNodeMode - - if lightMode && chainDisabled { - logger.Info("chain backend disabled - starting in ultra-light mode", - "full_node_mode", o.FullNodeMode, - "blockchain-rpc-endpoint", swapEndpoint) +func isChainEnabled(o *Options, logger log.Logger) bool { + if o.NodeMode == UltraLightMode { + logger.Info("chain backend disabled - starting in ultra-light mode") return false } - - logger.Info("chain backend enabled - blockchain functionality available", - "full_node_mode", o.FullNodeMode, - "blockchain-rpc-endpoint", swapEndpoint) - return true // all other modes operate require chain enabled + logger.Info("chain backend enabled - blockchain functionality available", "node_mode", o.NodeMode) + return true } func validatePublicAddress(addr string) error { From b58c43a0135b63c35fadcb6f70189182556992a3 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Tue, 14 Apr 2026 12:17:15 +0300 Subject: [PATCH 02/11] fix: restore legacy mode detection in resolveNodeMode --- .github/workflows/beekeeper.yml | 2 +- cmd/bee/cmd/start.go | 61 ++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/.github/workflows/beekeeper.yml b/.github/workflows/beekeeper.yml index 939d82b4f97..de1f6c1bf22 100644 --- a/.github/workflows/beekeeper.yml +++ b/.github/workflows/beekeeper.yml @@ -19,7 +19,7 @@ env: SETUP_CONTRACT_IMAGE: "ethersphere/bee-localchain" SETUP_CONTRACT_IMAGE_TAG: "0.9.4" BEELOCAL_BRANCH: "main" - BEEKEEPER_BRANCH: "master" + BEEKEEPER_BRANCH: "refactor/node-mode-config" BEEKEEPER_METRICS_ENABLED: false REACHABILITY_OVERRIDE_PUBLIC: true BATCHFACTOR_OVERRIDE_PUBLIC: 2 diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 9dc382d8f93..a1be35d0dac 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -342,46 +342,51 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo } // resolveNodeMode determines the effective node mode from config. -// --node-mode takes precedence; the deprecated --full-node flag is honoured as a fallback. -// It also validates that the required options for each mode are present. +// --node-mode takes precedence and triggers strict per-mode validation. +// The deprecated --full-node flag is honoured as a fallback. +// When neither is set, mode is inferred from blockchain-rpc-endpoint presence +// (legacy behaviour) without strict validation, for backward compatibility. func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) swapEnable := c.config.GetBool(optionNameSwapEnable) - // Resolve the mode: explicit node-mode wins, then legacy full-node, then default ultra-light. - var mode node.NodeMode if c.config.IsSet(optionNameNodeMode) { - mode = node.NodeMode(c.config.GetString(optionNameNodeMode)) + // Explicit node-mode: validate strictly. + mode := node.NodeMode(c.config.GetString(optionNameNodeMode)) if !mode.IsValid() { return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", mode) } - } else if c.config.GetBool(optionNameFullNode) { - logger.Warning("--full-node is deprecated, use --node-mode=full instead") - mode = node.FullMode - } else { - mode = node.UltraLightMode + switch mode { + case node.FullMode: + if rpcEndpoint == "" { + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") + } + if !swapEnable { + return "", errors.New("full node requires swap-enable to be true") + } + case node.LightMode: + if rpcEndpoint == "" { + return "", errors.New("light node requires blockchain-rpc-endpoint to be set") + } + case node.UltraLightMode: + if swapEnable { + return "", errors.New("ultra-light node cannot have swap-enable set to true") + } + } + return mode, nil } - // Validate mode-specific requirements. - switch mode { - case node.FullMode: - if rpcEndpoint == "" { - return "", errors.New("full node requires blockchain-rpc-endpoint to be set") - } - if !swapEnable { - return "", errors.New("full node requires swap-enable to be true") - } - case node.LightMode: - if rpcEndpoint == "" { - return "", errors.New("light node requires blockchain-rpc-endpoint to be set") - } - case node.UltraLightMode: - if swapEnable { - return "", errors.New("ultra-light node cannot have swap-enable set to true") - } + // Legacy path: node-mode not set, fall back to deprecated flags / old detection. + if c.config.GetBool(optionNameFullNode) { + logger.Warning("--full-node is deprecated, use --node-mode=full instead") + return node.FullMode, nil } - return mode, nil + // Infer light vs ultra-light from RPC endpoint presence (original behaviour). + if rpcEndpoint != "" { + return node.LightMode, nil + } + return node.UltraLightMode, nil } type program struct { From 9d736a05bf7354d94f30f24c41924f423a0d3db0 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Wed, 22 Apr 2026 17:26:35 +0300 Subject: [PATCH 03/11] test: add table-driven tests for resolveNodeMode --- cmd/bee/cmd/resolve_node_mode_test.go | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 cmd/bee/cmd/resolve_node_mode_test.go diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go new file mode 100644 index 00000000000..5880a3f57e7 --- /dev/null +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -0,0 +1,156 @@ +// Copyright 2026 The Swarm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "strings" + "testing" + + "github.com/ethersphere/bee/v2/pkg/log" + "github.com/ethersphere/bee/v2/pkg/node" + "github.com/spf13/viper" +) + +func TestResolveNodeMode(t *testing.T) { + tests := []struct { + name string + config map[string]any + wantMode node.NodeMode + wantErr string + }{ + // ── Explicit node-mode: strict validation ──────────────────────────────── + { + name: "full mode with rpc and swap succeeds", + config: map[string]any{ + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + }, + wantMode: node.FullMode, + }, + { + name: "full mode without rpc fails", + config: map[string]any{ + optionNameNodeMode: "full", + optionNameSwapEnable: true, + }, + wantErr: "full node requires blockchain-rpc-endpoint", + }, + { + name: "full mode without swap fails", + config: map[string]any{ + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantErr: "full node requires swap-enable", + }, + { + name: "light mode with rpc succeeds", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantMode: node.LightMode, + }, + { + name: "light mode without rpc fails", + config: map[string]any{ + optionNameNodeMode: "light", + }, + wantErr: "light node requires blockchain-rpc-endpoint", + }, + { + name: "ultra-light mode succeeds", + config: map[string]any{ + optionNameNodeMode: "ultra-light", + }, + wantMode: node.UltraLightMode, + }, + { + name: "ultra-light mode rejects swap-enable", + config: map[string]any{ + optionNameNodeMode: "ultra-light", + optionNameSwapEnable: true, + }, + wantErr: "ultra-light node cannot have swap-enable", + }, + { + name: "invalid node-mode value fails", + config: map[string]any{ + optionNameNodeMode: "superlight", + }, + wantErr: "invalid node-mode", + }, + + // ── Legacy path: no node-mode set ──────────────────────────────────────── + { + name: "legacy full-node true maps to full mode", + config: map[string]any{ + optionNameFullNode: true, + }, + wantMode: node.FullMode, + }, + { + name: "legacy with rpc endpoint infers light mode", + config: map[string]any{ + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantMode: node.LightMode, + }, + { + name: "legacy without rpc endpoint infers ultra-light mode", + config: map[string]any{}, + wantMode: node.UltraLightMode, + }, + { + // Beekeeper's inherited-config scenario: rpc + swap-enable without node-mode. + // Legacy path must NOT apply strict swap validation; this was the CI regression. + name: "legacy with rpc and swap-enable infers light without error", + config: map[string]any{ + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + }, + wantMode: node.LightMode, + }, + { + // Same scenario but for ultra-light: no rpc, swap-enable inherited from base. + name: "legacy without rpc but with swap-enable infers ultra-light without error", + config: map[string]any{ + optionNameSwapEnable: true, + }, + wantMode: node.UltraLightMode, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &command{ + config: viper.New(), + logger: log.Noop, + } + for k, v := range tt.config { + c.config.Set(k, v) + } + + gotMode, err := c.resolveNodeMode(c.logger) + + if tt.wantErr != "" { + if err == nil { + t.Fatalf("expected error containing %q, got nil (mode=%q)", tt.wantErr, gotMode) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("expected error containing %q, got %q", tt.wantErr, err.Error()) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gotMode != tt.wantMode { + t.Errorf("got mode %q, want %q", gotMode, tt.wantMode) + } + }) + } +} From 7e687994dc80500c35387d0bd7c2c87cdde15a99 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 30 Apr 2026 13:31:58 +0300 Subject: [PATCH 04/11] fix: tighten node-mode validation to prevent silent upgrade regressions --- cmd/bee/cmd/resolve_node_mode_test.go | 59 ++++++++++++++++++++++++--- cmd/bee/cmd/start.go | 47 +++++++++++++++++---- pkg/node/node.go | 2 +- 3 files changed, 93 insertions(+), 15 deletions(-) diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go index 5880a3f57e7..10ccb9017ab 100644 --- a/cmd/bee/cmd/resolve_node_mode_test.go +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -22,11 +22,13 @@ func TestResolveNodeMode(t *testing.T) { }{ // ── Explicit node-mode: strict validation ──────────────────────────────── { - name: "full mode with rpc and swap succeeds", + name: "full mode with rpc, swap, chequebook and incentives succeeds", config: map[string]any{ - optionNameNodeMode: "full", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, }, wantMode: node.FullMode, }, @@ -46,6 +48,35 @@ func TestResolveNodeMode(t *testing.T) { }, wantErr: "full node requires swap-enable", }, + { + name: "full mode without chequebook fails", + config: map[string]any{ + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameStorageIncentivesEnable: true, + }, + wantErr: "full node requires chequebook-enable", + }, + { + name: "full mode without storage-incentives fails", + config: map[string]any{ + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + }, + wantErr: "storage-incentives-enable", + }, + { + name: "chequebook-enable without swap-enable fails (light mode)", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameChequebookEnable: true, + }, + wantErr: "chequebook-enable requires swap-enable", + }, { name: "light mode with rpc succeeds", config: map[string]any{ @@ -86,12 +117,28 @@ func TestResolveNodeMode(t *testing.T) { // ── Legacy path: no node-mode set ──────────────────────────────────────── { - name: "legacy full-node true maps to full mode", + name: "legacy full-node true with all required flags maps to full mode", config: map[string]any{ - optionNameFullNode: true, + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, }, wantMode: node.FullMode, }, + { + // Upgraders relying on the old chequebook-enable=true default must + // now fail loudly instead of silently degrading to pseudo-settle. + name: "legacy full-node true without chequebook fails", + config: map[string]any{ + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameStorageIncentivesEnable: true, + }, + wantErr: "full node requires chequebook-enable", + }, { name: "legacy with rpc endpoint infers light mode", config: map[string]any{ diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index a1be35d0dac..68713bf42a1 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -343,26 +343,52 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo // resolveNodeMode determines the effective node mode from config. // --node-mode takes precedence and triggers strict per-mode validation. -// The deprecated --full-node flag is honoured as a fallback. +// The deprecated --full-node flag is honoured as a fallback and validated +// the same way when it requests full mode, so upgraders relying on the old +// chequebook-enable / storage-incentives-enable defaults fail loudly instead +// of silently degrading to pseudo-settle / no incentives. // When neither is set, mode is inferred from blockchain-rpc-endpoint presence // (legacy behaviour) without strict validation, for backward compatibility. func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) swapEnable := c.config.GetBool(optionNameSwapEnable) + chequebookEnable := c.config.GetBool(optionNameChequebookEnable) + incentivesEnable := c.config.GetBool(optionNameStorageIncentivesEnable) + + // chequebook init is gated on swap-enable in NewBee, so this combo is a + // silent no-op. Catch it eagerly regardless of mode. + if chequebookEnable && !swapEnable { + return "", errors.New("chequebook-enable requires swap-enable to be true") + } + + validateFullMode := func() error { + if rpcEndpoint == "" { + return errors.New("full node requires blockchain-rpc-endpoint to be set") + } + if !swapEnable { + return errors.New("full node requires swap-enable to be true") + } + if !chequebookEnable { + return errors.New("full node requires chequebook-enable to be true (cheque issuance)") + } + if !incentivesEnable { + return errors.New("full node requires storage-incentives-enable to be true") + } + return nil + } if c.config.IsSet(optionNameNodeMode) { - // Explicit node-mode: validate strictly. mode := node.NodeMode(c.config.GetString(optionNameNodeMode)) if !mode.IsValid() { return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", mode) } + if c.config.GetBool(optionNameFullNode) { + logger.Warning("--full-node is set alongside --node-mode; --full-node is ignored") + } switch mode { case node.FullMode: - if rpcEndpoint == "" { - return "", errors.New("full node requires blockchain-rpc-endpoint to be set") - } - if !swapEnable { - return "", errors.New("full node requires swap-enable to be true") + if err := validateFullMode(); err != nil { + return "", err } case node.LightMode: if rpcEndpoint == "" { @@ -376,9 +402,14 @@ func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { return mode, nil } - // Legacy path: node-mode not set, fall back to deprecated flags / old detection. + // Legacy path: node-mode not set. Apply strict validation when --full-node + // requests full mode so upgraders don't silently lose chequebook + + // incentives because of the new defaults. if c.config.GetBool(optionNameFullNode) { logger.Warning("--full-node is deprecated, use --node-mode=full instead") + if err := validateFullMode(); err != nil { + return "", err + } return node.FullMode, nil } diff --git a/pkg/node/node.go b/pkg/node/node.go index bd2ff2309a0..2bb92749d6b 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -553,7 +553,7 @@ func NewBee( } } - if o.SwapEnable { + if o.SwapEnable && chainEnabled { chequebookFactory, err := InitChequebookFactory(logger, chainBackend, chainID, transactionService, o.SwapFactoryAddress) if err != nil { return nil, fmt.Errorf("init chequebook factory: %w", err) From 479c7599affc05e63614cdff0ea2ea5d220f3cfd Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Wed, 3 Jun 2026 18:28:58 +0300 Subject: [PATCH 05/11] fix(node-mode): keep legacy --full-node configs starting after upgrade --- cmd/bee/cmd/resolve_node_mode_test.go | 88 +++++++++++++++++++++++++-- cmd/bee/cmd/start.go | 32 ++++++++-- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go index 10ccb9017ab..43c051f7cb7 100644 --- a/cmd/bee/cmd/resolve_node_mode_test.go +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -128,16 +128,63 @@ func TestResolveNodeMode(t *testing.T) { wantMode: node.FullMode, }, { - // Upgraders relying on the old chequebook-enable=true default must - // now fail loudly instead of silently degrading to pseudo-settle. - name: "legacy full-node true without chequebook fails", + // Upgrade compatibility: chequebook-enable and storage-incentives-enable + // defaulted to true before node-mode existed. A legacy --full-node + // config that did not set them explicitly is auto-enabled so the node + // still starts as a fully-functional full node instead of failing + // validation (no silent degradation). + name: "legacy full-node true auto-enables chequebook and incentives", + config: map[string]any{ + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + }, + wantMode: node.FullMode, + }, + { + // The compatibility default only restores the old implicit value; an + // operator who explicitly disables chequebook while requesting a full + // node has a genuine misconfiguration that must still fail loudly. + name: "legacy full-node true with chequebook explicitly false fails", + config: map[string]any{ + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameChequebookEnable: false, + }, + wantErr: "full node requires chequebook-enable", + }, + { + // Same: explicitly disabling storage incentives must not be masked. + name: "legacy full-node true with storage-incentives explicitly false fails", config: map[string]any{ optionNameFullNode: true, configKeyBlockchainRpcEndpoint: "http://localhost:8545", optionNameSwapEnable: true, - optionNameStorageIncentivesEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: false, }, - wantErr: "full node requires chequebook-enable", + wantErr: "storage-incentives-enable", + }, + { + // swap-enable was also implied by --full-node before node-mode; a + // legacy config with only the RPC endpoint set is fully restored and + // starts as a full node rather than failing. + name: "legacy full-node true with only rpc auto-enables full stack", + config: map[string]any{ + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantMode: node.FullMode, + }, + { + // The RPC endpoint is the one thing the compatibility default cannot + // invent; a legacy full-node config without it must still fail. + name: "legacy full-node true without rpc endpoint fails", + config: map[string]any{ + optionNameFullNode: true, + }, + wantErr: "full node requires blockchain-rpc-endpoint", }, { name: "legacy with rpc endpoint infers light mode", @@ -201,3 +248,34 @@ func TestResolveNodeMode(t *testing.T) { }) } } + +// TestResolveNodeModeLegacyBackcompatWritesConfig verifies that the legacy +// --full-node compatibility path writes the restored swap-enable, +// chequebook-enable and storage-incentives-enable defaults back into the +// config, so the rest of node startup (which reads them directly from config) +// sees them enabled. +func TestResolveNodeModeLegacyBackcompatWritesConfig(t *testing.T) { + c := &command{ + config: viper.New(), + logger: log.Noop, + } + c.config.Set(optionNameFullNode, true) + c.config.Set(configKeyBlockchainRpcEndpoint, "http://localhost:8545") + + mode, err := c.resolveNodeMode(c.logger) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mode != node.FullMode { + t.Fatalf("got mode %q, want %q", mode, node.FullMode) + } + for _, key := range []string{ + optionNameSwapEnable, + optionNameChequebookEnable, + optionNameStorageIncentivesEnable, + } { + if !c.config.GetBool(key) { + t.Errorf("expected %q to be written back as true", key) + } + } +} diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 7dba2948e86..34b697ba9f3 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -371,6 +371,31 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo // When neither is set, mode is inferred from blockchain-rpc-endpoint presence // (legacy behaviour) without strict validation, for backward compatibility. func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { + // Backward compatibility: before node-mode existed, --full-node implied a + // full node with swap, chequebook and storage incentives (chequebook and + // incentives even defaulted to true). With the new explicit defaults a + // legacy --full-node config that relied on them would fail strict full-mode + // validation and the node would not start. When --full-node is used as the + // legacy selector (node-mode unset), restore those implied values for any + // the operator did not set explicitly, so the upgraded node keeps running as + // a fully-functional full node (no silent degradation) instead of failing to + // start. Explicitly disabled options are left untouched and still fail + // validation below. The values are written back to config so the rest of + // node startup picks them up. + if c.config.GetBool(optionNameFullNode) && !c.config.IsSet(optionNameNodeMode) { + logger.Warning("--full-node is deprecated, use --node-mode=full instead") + for _, key := range []string{ + optionNameSwapEnable, + optionNameChequebookEnable, + optionNameStorageIncentivesEnable, + } { + if !c.config.IsSet(key) { + logger.Warning("enabling option implied by legacy --full-node; set it explicitly or use --node-mode=full", "option", key) + c.config.Set(key, true) + } + } + } + rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) swapEnable := c.config.GetBool(optionNameSwapEnable) chequebookEnable := c.config.GetBool(optionNameChequebookEnable) @@ -423,11 +448,10 @@ func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { return mode, nil } - // Legacy path: node-mode not set. Apply strict validation when --full-node - // requests full mode so upgraders don't silently lose chequebook + - // incentives because of the new defaults. + // Legacy path: node-mode not set. --full-node requests full mode; implied + // options were restored above, so any remaining failure is a genuine + // misconfiguration (missing rpc endpoint, or an explicitly disabled option). if c.config.GetBool(optionNameFullNode) { - logger.Warning("--full-node is deprecated, use --node-mode=full instead") if err := validateFullMode(); err != nil { return "", err } From ebc5f4aa64b07c397f071e3da87821b40607a0c6 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 27 Aug 2026 22:28:02 +0300 Subject: [PATCH 06/11] test(cmd): add edge-case and precedence tests for resolveNodeMode --- .golangci.yml | 4 +++ cmd/bee/cmd/resolve_node_mode_test.go | 48 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index ad34693a925..ae7330aaace 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -73,6 +73,10 @@ linters: - linters: - staticcheck text: "SA5008: malformed `json` tag: invalid trailing ',' character" + - linters: + - staticcheck + path: cmd/bee/cmd + text: "(this comparison is always true|never returns a nil interface value|the lhs of the comparison)" paths: - third_party$ - builtin$ diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go index 43c051f7cb7..c9e4ad9fc4d 100644 --- a/cmd/bee/cmd/resolve_node_mode_test.go +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -216,6 +216,54 @@ func TestResolveNodeMode(t *testing.T) { }, wantMode: node.UltraLightMode, }, + { + name: "node-mode takes precedence over legacy full-node with warning", + config: map[string]any{ + optionNameNodeMode: "light", + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantMode: node.LightMode, + }, + { + name: "ultra-light with rpc endpoint ignores rpc and succeeds", + config: map[string]any{ + optionNameNodeMode: "ultra-light", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + }, + wantMode: node.UltraLightMode, + }, + { + name: "light mode with rpc, swap and chequebook succeeds", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + }, + wantMode: node.LightMode, + }, + { + name: "empty node-mode string fails validation", + config: map[string]any{ + optionNameNodeMode: "", + }, + wantErr: "invalid node-mode", + }, + { + name: "uppercase node-mode string fails validation", + config: map[string]any{ + optionNameNodeMode: "FULL", + }, + wantErr: "invalid node-mode", + }, + { + name: "whitespace node-mode string fails validation", + config: map[string]any{ + optionNameNodeMode: " full ", + }, + wantErr: "invalid node-mode", + }, } for _, tt := range tests { From 8b061de5b3eca62bb76935d7b09cc97561d5299d Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Wed, 2 Sep 2026 13:07:12 +0300 Subject: [PATCH 07/11] fix(cmd): reject storage incentives in light and ultra-light modes --- cmd/bee/cmd/resolve_node_mode_test.go | 17 +++++++++++++++++ cmd/bee/cmd/start.go | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go index c9e4ad9fc4d..085fb1d8417 100644 --- a/cmd/bee/cmd/resolve_node_mode_test.go +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -92,6 +92,15 @@ func TestResolveNodeMode(t *testing.T) { }, wantErr: "light node requires blockchain-rpc-endpoint", }, + { + name: "light mode rejects storage-incentives-enable", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameStorageIncentivesEnable: true, + }, + wantErr: "light node cannot have storage-incentives-enable", + }, { name: "ultra-light mode succeeds", config: map[string]any{ @@ -107,6 +116,14 @@ func TestResolveNodeMode(t *testing.T) { }, wantErr: "ultra-light node cannot have swap-enable", }, + { + name: "ultra-light mode rejects storage-incentives-enable", + config: map[string]any{ + optionNameNodeMode: "ultra-light", + optionNameStorageIncentivesEnable: true, + }, + wantErr: "ultra-light node cannot have storage-incentives-enable", + }, { name: "invalid node-mode value fails", config: map[string]any{ diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 392e33804cb..0b1a833b3b4 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -451,10 +451,16 @@ func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { if rpcEndpoint == "" { return "", errors.New("light node requires blockchain-rpc-endpoint to be set") } + if incentivesEnable { + return "", errors.New("light node cannot have storage-incentives-enable set to true") + } case node.UltraLightMode: if swapEnable { return "", errors.New("ultra-light node cannot have swap-enable set to true") } + if incentivesEnable { + return "", errors.New("ultra-light node cannot have storage-incentives-enable set to true") + } } return mode, nil } From 5ef43da3165f931887c1039615f83a4024cb0399 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 3 Sep 2026 13:39:34 +0300 Subject: [PATCH 08/11] refactor(cmd): resolve node-mode in two regimes 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. --- cmd/bee/cmd/cmd.go | 2 +- cmd/bee/cmd/resolve_node_mode_test.go | 471 ++++++++++++++++++-------- cmd/bee/cmd/start.go | 195 ++++++----- 3 files changed, 428 insertions(+), 240 deletions(-) diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index adf8a33cb39..23056913c0d 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -358,7 +358,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory addresses") cmd.Flags().String(optionNameBzzTokenAddress, "", "bzz token contract address") cmd.Flags().String(optionNameSwapInitialDeposit, "0", "initial deposit if deploying a new chequebook") - cmd.Flags().String(optionNameNodeMode, string(node.UltraLightMode), "node operational mode: full, light, or ultra-light") + cmd.Flags().String(optionNameNodeMode, "", "node operational mode: full, light, or ultra-light (unset: inferred from deprecated full-node and blockchain-rpc-endpoint)") cmd.Flags().Bool(optionNameSwapEnable, false, "enable swap") cmd.Flags().Bool(optionNameChequebookEnable, false, "enable chequebook (requires swap-enable)") cmd.Flags().Bool(optionNameChequebookVerification, false, "reject full-node hive/handshake records that carry no chequebook address") diff --git a/cmd/bee/cmd/resolve_node_mode_test.go b/cmd/bee/cmd/resolve_node_mode_test.go index 085fb1d8417..c9c9e5c6fdb 100644 --- a/cmd/bee/cmd/resolve_node_mode_test.go +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -13,19 +13,39 @@ import ( "github.com/spf13/viper" ) +const testRPCEndpoint = "http://localhost:8545" + func TestResolveNodeMode(t *testing.T) { + t.Parallel() + tests := []struct { name string config map[string]any wantMode node.NodeMode wantErr string + // wantOptions holds the values the resolver must leave in config for the + // rest of startup to read. Keys not listed are not checked. + wantOptions map[string]bool }{ - // ── Explicit node-mode: strict validation ──────────────────────────────── + // ── node-mode set: the mode owns the config ────────────────────────────── + { + name: "full with rpc only implies swap, chequebook and incentives", + config: map[string]any{ + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, + }, + }, { - name: "full mode with rpc, swap, chequebook and incentives succeeds", + name: "full with all options explicitly enabled succeeds", config: map[string]any{ optionNameNodeMode: "full", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, optionNameSwapEnable: true, optionNameChequebookEnable: true, optionNameStorageIncentivesEnable: true, @@ -33,83 +53,158 @@ func TestResolveNodeMode(t *testing.T) { wantMode: node.FullMode, }, { - name: "full mode without rpc fails", + name: "full without rpc fails", config: map[string]any{ - optionNameNodeMode: "full", - optionNameSwapEnable: true, + optionNameNodeMode: "full", }, wantErr: "full node requires blockchain-rpc-endpoint", }, { - name: "full mode without swap fails", + // A non-staking full node is a legitimate opt-out. + name: "full with storage-incentives explicitly false honours the opt-out", config: map[string]any{ - optionNameNodeMode: "full", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: false, }, - wantErr: "full node requires swap-enable", }, { - name: "full mode without chequebook fails", + // Disabling swap must not drag an implied chequebook into a + // contradiction the operator never wrote. + name: "full with swap explicitly false leaves chequebook off", config: map[string]any{ - optionNameNodeMode: "full", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: false, optionNameStorageIncentivesEnable: true, }, - wantErr: "full node requires chequebook-enable", }, { - name: "full mode without storage-incentives fails", + // Receive-only swap: cash out cheques without issuing them. + name: "full with chequebook explicitly false keeps swap on", config: map[string]any{ optionNameNodeMode: "full", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, - optionNameChequebookEnable: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameChequebookEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: true, + optionNameChequebookEnable: false, }, - wantErr: "storage-incentives-enable", }, { - name: "chequebook-enable without swap-enable fails (light mode)", + name: "full with chequebook explicitly true and swap explicitly false fails", config: map[string]any{ - optionNameNodeMode: "light", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameNodeMode: "full", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: false, optionNameChequebookEnable: true, }, wantErr: "chequebook-enable requires swap-enable", }, { - name: "light mode with rpc succeeds", + // NewBee never starts swap, push-sync or the incentives agent for a + // bootnode, so full mode must not imply them there. + name: "full bootnode does not imply swap, chequebook or incentives", + config: map[string]any{ + optionNameNodeMode: "full", + optionNameBootnodeMode: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, + }, + }, + { + name: "full bootnode with storage-incentives explicitly false succeeds", + config: map[string]any{ + optionNameNodeMode: "full", + optionNameBootnodeMode: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + }, + { + name: "light with rpc succeeds without swap", config: map[string]any{ optionNameNodeMode: "light", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, }, wantMode: node.LightMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, + }, }, { - name: "light mode without rpc fails", + name: "light with rpc, swap and chequebook succeeds", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + }, + wantMode: node.LightMode, + }, + { + name: "light without rpc fails", config: map[string]any{ optionNameNodeMode: "light", }, wantErr: "light node requires blockchain-rpc-endpoint", }, { - name: "light mode rejects storage-incentives-enable", + name: "light with chequebook but no swap fails", + config: map[string]any{ + optionNameNodeMode: "light", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameChequebookEnable: true, + }, + wantErr: "chequebook-enable requires swap-enable", + }, + { + name: "light rejects storage-incentives-enable", config: map[string]any{ optionNameNodeMode: "light", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, optionNameStorageIncentivesEnable: true, }, wantErr: "light node cannot have storage-incentives-enable", }, { - name: "ultra-light mode succeeds", + name: "ultra-light succeeds", config: map[string]any{ optionNameNodeMode: "ultra-light", }, wantMode: node.UltraLightMode, }, { - name: "ultra-light mode rejects swap-enable", + name: "ultra-light with rpc ignores rpc and succeeds", + config: map[string]any{ + optionNameNodeMode: "ultra-light", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.UltraLightMode, + }, + { + name: "ultra-light rejects swap-enable", config: map[string]any{ optionNameNodeMode: "ultra-light", optionNameSwapEnable: true, @@ -117,13 +212,22 @@ func TestResolveNodeMode(t *testing.T) { wantErr: "ultra-light node cannot have swap-enable", }, { - name: "ultra-light mode rejects storage-incentives-enable", + name: "ultra-light rejects storage-incentives-enable", config: map[string]any{ optionNameNodeMode: "ultra-light", optionNameStorageIncentivesEnable: true, }, wantErr: "ultra-light node cannot have storage-incentives-enable", }, + { + name: "node-mode takes precedence over legacy full-node", + config: map[string]any{ + optionNameNodeMode: "light", + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.LightMode, + }, { name: "invalid node-mode value fails", config: map[string]any{ @@ -131,160 +235,146 @@ func TestResolveNodeMode(t *testing.T) { }, wantErr: "invalid node-mode", }, + { + name: "uppercase node-mode fails", + config: map[string]any{ + optionNameNodeMode: "FULL", + }, + wantErr: "invalid node-mode", + }, + { + name: "whitespace node-mode fails", + config: map[string]any{ + optionNameNodeMode: " full ", + }, + wantErr: "invalid node-mode", + }, - // ── Legacy path: no node-mode set ──────────────────────────────────────── + // ── node-mode unset: legacy behaviour, verbatim ───────────────────────── { - name: "legacy full-node true with all required flags maps to full mode", + // The most common pre-node-mode light config. chequebook-enable used + // to default to true, so this node issued cheques; it must keep doing so. + name: "legacy light with swap only restores chequebook default", config: map[string]any{ - optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: true, + }, + wantMode: node.LightMode, + wantOptions: map[string]bool{ optionNameSwapEnable: true, optionNameChequebookEnable: true, optionNameStorageIncentivesEnable: true, }, - wantMode: node.FullMode, }, { - // Upgrade compatibility: chequebook-enable and storage-incentives-enable - // defaulted to true before node-mode existed. A legacy --full-node - // config that did not set them explicitly is auto-enabled so the node - // still starts as a fully-functional full node instead of failing - // validation (no silent degradation). - name: "legacy full-node true auto-enables chequebook and incentives", + // The old shipped default; chequebook stays gated on swap in NewBee. + name: "legacy chequebook without swap starts", config: map[string]any{ - optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, + optionNameChequebookEnable: true, }, - wantMode: node.FullMode, + wantMode: node.UltraLightMode, }, { - // The compatibility default only restores the old implicit value; an - // operator who explicitly disables chequebook while requesting a full - // node has a genuine misconfiguration that must still fail loudly. - name: "legacy full-node true with chequebook explicitly false fails", + name: "legacy full-node with rpc only restores old defaults and leaves swap off", config: map[string]any{ optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, - optionNameChequebookEnable: false, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, }, - wantErr: "full node requires chequebook-enable", }, { - // Same: explicitly disabling storage incentives must not be masked. - name: "legacy full-node true with storage-incentives explicitly false fails", + name: "legacy full-node with all options set maps to full", config: map[string]any{ optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, optionNameSwapEnable: true, optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, + }, + wantMode: node.FullMode, + }, + { + name: "legacy full-node with explicit opt-outs is not validated", + config: map[string]any{ + optionNameFullNode: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: false, + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: false, optionNameStorageIncentivesEnable: false, }, - wantErr: "storage-incentives-enable", }, { - // swap-enable was also implied by --full-node before node-mode; a - // legacy config with only the RPC endpoint set is fully restored and - // starts as a full node rather than failing. - name: "legacy full-node true with only rpc auto-enables full stack", + name: "legacy bootnode with storage-incentives false starts", config: map[string]any{ - optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + optionNameFullNode: true, + optionNameBootnodeMode: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameStorageIncentivesEnable: false, }, wantMode: node.FullMode, }, { - // The RPC endpoint is the one thing the compatibility default cannot - // invent; a legacy full-node config without it must still fail. - name: "legacy full-node true without rpc endpoint fails", + // Previous releases enabled the chain backend for every full node and + // failed at chain init without an endpoint; keep failing, earlier. + name: "legacy full-node without rpc fails", config: map[string]any{ optionNameFullNode: true, }, wantErr: "full node requires blockchain-rpc-endpoint", }, { - name: "legacy with rpc endpoint infers light mode", + name: "legacy with rpc infers light", config: map[string]any{ - configKeyBlockchainRpcEndpoint: "http://localhost:8545", + configKeyBlockchainRpcEndpoint: testRPCEndpoint, }, wantMode: node.LightMode, }, { - name: "legacy without rpc endpoint infers ultra-light mode", + name: "legacy without rpc infers ultra-light", config: map[string]any{}, wantMode: node.UltraLightMode, }, { - // Beekeeper's inherited-config scenario: rpc + swap-enable without node-mode. - // Legacy path must NOT apply strict swap validation; this was the CI regression. - name: "legacy with rpc and swap-enable infers light without error", - config: map[string]any{ - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, - }, - wantMode: node.LightMode, - }, - { - // Same scenario but for ultra-light: no rpc, swap-enable inherited from base. - name: "legacy without rpc but with swap-enable infers ultra-light without error", + // Beekeeper's inherited-config scenario: swap-enable inherited from the + // base profile on a node without rpc. Legacy must not validate it. + name: "legacy without rpc but with swap infers ultra-light without error", config: map[string]any{ optionNameSwapEnable: true, }, wantMode: node.UltraLightMode, }, { - name: "node-mode takes precedence over legacy full-node with warning", - config: map[string]any{ - optionNameNodeMode: "light", - optionNameFullNode: true, - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - }, - wantMode: node.LightMode, - }, - { - name: "ultra-light with rpc endpoint ignores rpc and succeeds", - config: map[string]any{ - optionNameNodeMode: "ultra-light", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - }, - wantMode: node.UltraLightMode, - }, - { - name: "light mode with rpc, swap and chequebook succeeds", + // Legacy must not restore a default the operator overrode. + name: "legacy explicit false is preserved", config: map[string]any{ - optionNameNodeMode: "light", - configKeyBlockchainRpcEndpoint: "http://localhost:8545", - optionNameSwapEnable: true, - optionNameChequebookEnable: true, + configKeyBlockchainRpcEndpoint: testRPCEndpoint, + optionNameSwapEnable: true, + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, }, wantMode: node.LightMode, - }, - { - name: "empty node-mode string fails validation", - config: map[string]any{ - optionNameNodeMode: "", - }, - wantErr: "invalid node-mode", - }, - { - name: "uppercase node-mode string fails validation", - config: map[string]any{ - optionNameNodeMode: "FULL", - }, - wantErr: "invalid node-mode", - }, - { - name: "whitespace node-mode string fails validation", - config: map[string]any{ - optionNameNodeMode: " full ", + wantOptions: map[string]bool{ + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, }, - wantErr: "invalid node-mode", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := &command{ config: viper.New(), logger: log.Noop, @@ -310,37 +400,118 @@ func TestResolveNodeMode(t *testing.T) { if gotMode != tt.wantMode { t.Errorf("got mode %q, want %q", gotMode, tt.wantMode) } + for key, want := range tt.wantOptions { + if got := c.config.GetBool(key); got != want { + t.Errorf("option %q: got %t, want %t", key, got, want) + } + } }) } } -// TestResolveNodeModeLegacyBackcompatWritesConfig verifies that the legacy -// --full-node compatibility path writes the restored swap-enable, -// chequebook-enable and storage-incentives-enable defaults back into the -// config, so the rest of node startup (which reads them directly from config) -// sees them enabled. -func TestResolveNodeModeLegacyBackcompatWritesConfig(t *testing.T) { - c := &command{ - config: viper.New(), - logger: log.Noop, - } - c.config.Set(optionNameFullNode, true) - c.config.Set(configKeyBlockchainRpcEndpoint, "http://localhost:8545") +// TestResolveNodeModeWithBoundFlags runs the resolver against a viper bound to +// the real start command flags, as production does, so the flag defaults are +// exercised: an unset node-mode must select the legacy regime, and the flag +// defaults of the sub-options must not count as explicitly set. +func TestResolveNodeModeWithBoundFlags(t *testing.T) { + t.Parallel() - mode, err := c.resolveNodeMode(c.logger) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if mode != node.FullMode { - t.Fatalf("got mode %q, want %q", mode, node.FullMode) + tests := []struct { + name string + args []string + wantMode node.NodeMode + wantOptions map[string]bool + }{ + { + name: "no flags selects legacy regime and restores old defaults", + args: nil, + wantMode: node.UltraLightMode, + wantOptions: map[string]bool{ + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, + }, + }, + { + name: "legacy full-node flag with rpc", + args: []string{"--full-node", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, + }, + }, + { + name: "node-mode full with rpc implies the full stack", + args: []string{"--node-mode=full", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: true, + }, + }, + { + name: "node-mode light with rpc keeps sub-option defaults", + args: []string{"--node-mode=light", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: false, + optionNameChequebookEnable: false, + optionNameStorageIncentivesEnable: false, + }, + }, + { + name: "node-mode full with incentives explicitly disabled", + args: []string{"--node-mode=full", "--blockchain-rpc-endpoint=" + testRPCEndpoint, "--storage-incentives-enable=false"}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + optionNameSwapEnable: true, + optionNameChequebookEnable: true, + optionNameStorageIncentivesEnable: false, + }, + }, } - for _, key := range []string{ - optionNameSwapEnable, - optionNameChequebookEnable, - optionNameStorageIncentivesEnable, - } { - if !c.config.GetBool(key) { - t.Errorf("expected %q to be written back as true", key) - } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + root, err := newCommand(func(c *command) { c.homeDir = t.TempDir() }) + if err != nil { + t.Fatal(err) + } + startCmd := root.SubCommandForTest("start") + if startCmd == nil { + t.Fatal("start subcommand not found") + } + if err := startCmd.ParseFlags(tt.args); err != nil { + t.Fatal(err) + } + + // Mirror the start command's PreRunE: bind flags, then map the flat + // blockchain-rpc-* flags onto their nested config keys. + c := &command{ + config: viper.New(), + logger: log.Noop, + } + if err := c.config.BindPFlags(startCmd.Flags()); err != nil { + t.Fatal(err) + } + c.bindBlockchainRpcConfig(startCmd) + + gotMode, err := c.resolveNodeMode(c.logger) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if gotMode != tt.wantMode { + t.Errorf("got mode %q, want %q", gotMode, tt.wantMode) + } + for key, want := range tt.wantOptions { + if got := c.config.GetBool(key); got != want { + t.Errorf("option %q: got %t, want %t", key, got, want) + } + } + }) } } diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 0b1a833b3b4..2cff7db0fc4 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -373,113 +373,130 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo return b, err } -// resolveNodeMode determines the effective node mode from config. -// --node-mode takes precedence and triggers strict per-mode validation. -// The deprecated --full-node flag is honoured as a fallback and validated -// the same way when it requests full mode, so upgraders relying on the old -// chequebook-enable / storage-incentives-enable defaults fail loudly instead -// of silently degrading to pseudo-settle / no incentives. -// When neither is set, mode is inferred from blockchain-rpc-endpoint presence -// (legacy behaviour) without strict validation, for backward compatibility. +// legacyNodeModeRemovalVersion is the release in which node-mode inference from +// the deprecated full-node option and blockchain-rpc-endpoint presence is +// removed. From that release on, node-mode is required. +const legacyNodeModeRemovalVersion = "v2.11.0" + +// resolveNodeMode determines the effective node mode from config. There are +// two regimes, selected by whether node-mode is set. +// +// node-mode unset (legacy): the node behaves exactly as releases before +// node-mode existed. chequebook-enable and storage-incentives-enable fall back +// to their former default of true when not set, the mode is inferred from the +// deprecated full-node option and the presence of blockchain-rpc-endpoint, and +// no further validation is applied. A deprecation warning names the equivalent +// node-mode value and the release in which the inference is removed. +// +// node-mode set: the mode owns the config. Options the mode implies are +// enabled unless the operator explicitly disabled them, options the mode +// cannot support are rejected when explicitly enabled, and +// blockchain-rpc-endpoint is required for light and full nodes. Implied values +// are written back to config so the rest of node startup picks them up. func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { - // Backward compatibility: before node-mode existed, --full-node implied a - // full node with swap, chequebook and storage incentives (chequebook and - // incentives even defaulted to true). With the new explicit defaults a - // legacy --full-node config that relied on them would fail strict full-mode - // validation and the node would not start. When --full-node is used as the - // legacy selector (node-mode unset), restore those implied values for any - // the operator did not set explicitly, so the upgraded node keeps running as - // a fully-functional full node (no silent degradation) instead of failing to - // start. Explicitly disabled options are left untouched and still fail - // validation below. The values are written back to config so the rest of - // node startup picks them up. - if c.config.GetBool(optionNameFullNode) && !c.config.IsSet(optionNameNodeMode) { - logger.Warning("--full-node is deprecated, use --node-mode=full instead") - for _, key := range []string{ - optionNameSwapEnable, - optionNameChequebookEnable, - optionNameStorageIncentivesEnable, - } { - if !c.config.IsSet(key) { - logger.Warning("enabling option implied by legacy --full-node; set it explicitly or use --node-mode=full", "option", key) - c.config.Set(key, true) - } - } + modeStr := c.config.GetString(optionNameNodeMode) + if modeStr == "" { + return c.resolveLegacyNodeMode(logger) } - rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) - swapEnable := c.config.GetBool(optionNameSwapEnable) - chequebookEnable := c.config.GetBool(optionNameChequebookEnable) - incentivesEnable := c.config.GetBool(optionNameStorageIncentivesEnable) - - // chequebook init is gated on swap-enable in NewBee, so this combo is a - // silent no-op. Catch it eagerly regardless of mode. - if chequebookEnable && !swapEnable { - return "", errors.New("chequebook-enable requires swap-enable to be true") + mode := node.NodeMode(modeStr) + if !mode.IsValid() { + return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", mode) + } + if c.config.GetBool(optionNameFullNode) { + logger.Warning("--full-node is set alongside --node-mode; --full-node is ignored") } - validateFullMode := func() error { + rpcEndpoint := c.config.GetString(configKeyBlockchainRpcEndpoint) + + switch mode { + case node.FullMode: if rpcEndpoint == "" { - return errors.New("full node requires blockchain-rpc-endpoint to be set") + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") } - if !swapEnable { - return errors.New("full node requires swap-enable to be true") + // A full node implies swap, chequebook and storage incentives. Bootnodes + // are exempt: NewBee never starts swap, push-sync or the incentives agent + // for them, so implying the options would only cost a chequebook deploy. + if !c.config.GetBool(optionNameBootnodeMode) { + c.enableImpliedOption(logger, mode, optionNameSwapEnable) + if c.config.GetBool(optionNameSwapEnable) { + c.enableImpliedOption(logger, mode, optionNameChequebookEnable) + } + c.enableImpliedOption(logger, mode, optionNameStorageIncentivesEnable) } - if !chequebookEnable { - return errors.New("full node requires chequebook-enable to be true (cheque issuance)") + case node.LightMode: + if rpcEndpoint == "" { + return "", errors.New("light node requires blockchain-rpc-endpoint to be set") } - if !incentivesEnable { - return errors.New("full node requires storage-incentives-enable to be true") + if c.config.GetBool(optionNameStorageIncentivesEnable) { + return "", errors.New("light node cannot have storage-incentives-enable set to true") } - return nil - } - - if c.config.IsSet(optionNameNodeMode) { - mode := node.NodeMode(c.config.GetString(optionNameNodeMode)) - if !mode.IsValid() { - return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", mode) + case node.UltraLightMode: + if c.config.GetBool(optionNameSwapEnable) { + return "", errors.New("ultra-light node cannot have swap-enable set to true") } - if c.config.GetBool(optionNameFullNode) { - logger.Warning("--full-node is set alongside --node-mode; --full-node is ignored") + if c.config.GetBool(optionNameStorageIncentivesEnable) { + return "", errors.New("ultra-light node cannot have storage-incentives-enable set to true") } - switch mode { - case node.FullMode: - if err := validateFullMode(); err != nil { - return "", err - } - case node.LightMode: - if rpcEndpoint == "" { - return "", errors.New("light node requires blockchain-rpc-endpoint to be set") - } - if incentivesEnable { - return "", errors.New("light node cannot have storage-incentives-enable set to true") - } - case node.UltraLightMode: - if swapEnable { - return "", errors.New("ultra-light node cannot have swap-enable set to true") - } - if incentivesEnable { - return "", errors.New("ultra-light node cannot have storage-incentives-enable set to true") - } - } - return mode, nil } - // Legacy path: node-mode not set. --full-node requests full mode; implied - // options were restored above, so any remaining failure is a genuine - // misconfiguration (missing rpc endpoint, or an explicitly disabled option). - if c.config.GetBool(optionNameFullNode) { - if err := validateFullMode(); err != nil { - return "", err + // chequebook init is gated on swap-enable in NewBee. With node-mode set the + // implied values above never produce this combination, so it is always an + // explicit contradiction rather than a silent no-op. + if c.config.GetBool(optionNameChequebookEnable) && !c.config.GetBool(optionNameSwapEnable) { + return "", errors.New("chequebook-enable requires swap-enable to be true") + } + + return mode, nil +} + +// enableImpliedOption turns on an option implied by the node mode unless the +// operator set it explicitly. An explicit false is honoured and reported, since +// it degrades the node relative to what the mode normally provides. +func (c *command) enableImpliedOption(logger log.Logger, mode node.NodeMode, key string) { + if !c.config.IsSet(key) { + logger.Debug("enabling option implied by node-mode", "node_mode", mode, "option", key) + c.config.Set(key, true) + return + } + if !c.config.GetBool(key) { + logger.Warning("option implied by node-mode is explicitly disabled", "node_mode", mode, "option", key) + } +} + +// resolveLegacyNodeMode reproduces the behaviour of releases before node-mode +// existed, for configs that do not set node-mode. The only check kept is that +// a full node has a blockchain-rpc-endpoint: those releases enabled the chain +// backend for every full node and failed at chain init without one, so the +// early error changes the message, not the outcome. +func (c *command) resolveLegacyNodeMode(logger log.Logger) (node.NodeMode, error) { + // chequebook-enable and storage-incentives-enable used to default to true. + // Restore that for configs that leave them unset so an upgraded node keeps + // its settlement and incentives behaviour. Both stay gated in NewBee + // (chequebook on swap-enable, incentives on full mode), so this cannot start + // anything the previous release would not have started. + for _, key := range []string{optionNameChequebookEnable, optionNameStorageIncentivesEnable} { + if !c.config.IsSet(key) { + c.config.Set(key, true) } - return node.FullMode, nil } - // Infer light vs ultra-light from RPC endpoint presence (original behaviour). - if rpcEndpoint != "" { - return node.LightMode, nil + mode := node.UltraLightMode + switch { + case c.config.GetBool(optionNameFullNode): + if c.config.GetString(configKeyBlockchainRpcEndpoint) == "" { + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") + } + mode = node.FullMode + case c.config.GetString(configKeyBlockchainRpcEndpoint) != "": + mode = node.LightMode } - return node.UltraLightMode, nil + + logger.Warning("node-mode is not set and was inferred from legacy options; add it to your config, inference will be removed", + "node_mode", mode, + "removed_in", legacyNodeModeRemovalVersion, + ) + return mode, nil } type program struct { From 1bf0f0c92268cb28adb32e4655500a9e20764c92 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 3 Sep 2026 13:39:34 +0300 Subject: [PATCH 09/11] fix(packaging): pass BEE_NODE_MODE through docker compose and refresh 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. --- packaging/bee.yaml | 11 +++++++---- packaging/docker/README.md | 2 +- packaging/docker/docker-compose.yml | 1 + packaging/docker/env | 12 +++++++----- packaging/homebrew-amd64/bee.yaml | 11 +++++++---- packaging/homebrew-arm64/bee.yaml | 11 +++++++---- packaging/scoop/bee.yaml | 11 +++++++---- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packaging/bee.yaml b/packaging/bee.yaml index 786989dec0f..0c94069ee94 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -2,9 +2,12 @@ ## ── Node mode ──────────────────────────────────────────────────────────────── ## Selects the operational mode of this node. -## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## full - participates in storage and incentives; requires blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable ## light - uploads and downloads only; requires blockchain-rpc -## ultra-light - free-tier downloads only; no blockchain connection needed (default) +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset, the mode is inferred from the deprecated full-node option and +## blockchain-rpc presence (deprecated, removed in v2.11.0). # node-mode: ultra-light ## ── Blockchain / RPC (required for full and light nodes) ───────────────────── @@ -23,7 +26,7 @@ ## ── Swap / chequebook (full and light nodes only) ──────────────────────────── ## enable swap # swap-enable: false -## enable chequebook +## enable chequebook (requires swap-enable; implied by node-mode: full) # chequebook-enable: false ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false @@ -33,7 +36,7 @@ # swap-initial-deposit: "0" ## ── Full node only ──────────────────────────────────────────────────────────── -## enable storage incentives feature +## enable storage incentives feature (implied by node-mode: full) # storage-incentives-enable: false ## reserve capacity doubling # reserve-capacity-doubling: 0 diff --git a/packaging/docker/README.md b/packaging/docker/README.md index db118ff1ef0..ee73934432c 100644 --- a/packaging/docker/README.md +++ b/packaging/docker/README.md @@ -11,7 +11,7 @@ wget -q https://raw.githubusercontent.com/ethersphere/bee/master/packaging/docke Set all configuration variables inside `.env` -If you want to run node in full mode, set `BEE_FULL_NODE=true` +Select the node mode with `BEE_NODE_MODE` (`full`, `light`, or `ultra-light`). A full node needs `BEE_BLOCKCHAIN_RPC_ENDPOINT` and implies swap, chequebook and storage incentives. Bee requires an Ethereum endpoint to function. Obtain a free Infura account and set: diff --git a/packaging/docker/docker-compose.yml b/packaging/docker/docker-compose.yml index 16818215c7c..4f8ba110f12 100644 --- a/packaging/docker/docker-compose.yml +++ b/packaging/docker/docker-compose.yml @@ -39,6 +39,7 @@ services: - BEE_NAT_WSS_ADDR - BEE_NEIGHBORHOOD_SUGGESTER - BEE_NETWORK_ID + - BEE_NODE_MODE - BEE_P2P_ADDR - BEE_P2P_WS_ENABLE - BEE_P2P_WSS_ADDR diff --git a/packaging/docker/env b/packaging/docker/env index f33a62c0f15..cfb51cbbafb 100644 --- a/packaging/docker/env +++ b/packaging/docker/env @@ -37,8 +37,8 @@ # BEE_CACHE_CAPACITY=1000000 ## enable forwarded content caching (default true) # BEE_CACHE_RETRIEVAL=true -## enable chequebook (default true) -# BEE_CHEQUEBOOK_ENABLE=true +## enable chequebook; requires swap; implied by BEE_NODE_MODE=full (default false) +# BEE_CHEQUEBOOK_ENABLE=false ## reject full-node hive/handshake records that carry no chequebook address # BEE_CHEQUEBOOK_VERIFICATION=false ## origins with CORS headers enabled (default []) @@ -53,7 +53,7 @@ # BEE_DB_OPEN_FILES_LIMIT=200 ## size of the database write buffer in bytes (default 33554432) # BEE_DB_WRITE_BUFFER_SIZE=33554432 -## cause the node to start in full mode (default false) +## cause the node to start in full mode (deprecated: use BEE_NODE_MODE=full) (default false) # BEE_FULL_NODE=false ## gas limit fallback when estimation fails for contract transactions (default 500000) # BEE_GAS_LIMIT_FALLBACK=500000 @@ -71,6 +71,8 @@ # BEE_NEIGHBORHOOD_SUGGESTER=https://api.swarmscan.io/v1/network/neighborhoods/suggestion ## ID of the Swarm network (default mainnet id from bee) # BEE_NETWORK_ID=1 +## node operational mode: full, light, or ultra-light; unset infers the mode from BEE_FULL_NODE and BEE_BLOCKCHAIN_RPC_ENDPOINT (deprecated) +# BEE_NODE_MODE=ultra-light ## P2P listen address (default :1634) # BEE_P2P_ADDR=:1634 ## enable P2P WebSocket transport (default false) @@ -115,8 +117,8 @@ # BEE_STATIC_NODES=[] ## lru memory caching capacity in number of statestore entries (default 100000) # BEE_STATESTORE_CACHE_CAPACITY=100000 -## enable storage incentives feature (default true) -# BEE_STORAGE_INCENTIVES_ENABLE=true +## enable storage incentives feature; full node only; implied by BEE_NODE_MODE=full (default false) +# BEE_STORAGE_INCENTIVES_ENABLE=false ## enable swap (default false) # BEE_SWAP_ENABLE=false ## swap factory addresses (default empty) diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index 5967d6e56fa..315b58689bd 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -2,9 +2,12 @@ ## ── Node mode ──────────────────────────────────────────────────────────────── ## Selects the operational mode of this node. -## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## full - participates in storage and incentives; requires blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable ## light - uploads and downloads only; requires blockchain-rpc -## ultra-light - free-tier downloads only; no blockchain connection needed (default) +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset, the mode is inferred from the deprecated full-node option and +## blockchain-rpc presence (deprecated, removed in v2.11.0). # node-mode: ultra-light ## ── Blockchain / RPC (required for full and light nodes) ───────────────────── @@ -23,7 +26,7 @@ ## ── Swap / chequebook (full and light nodes only) ──────────────────────────── ## enable swap # swap-enable: false -## enable chequebook +## enable chequebook (requires swap-enable; implied by node-mode: full) # chequebook-enable: false ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false @@ -33,7 +36,7 @@ # swap-initial-deposit: "0" ## ── Full node only ──────────────────────────────────────────────────────────── -## enable storage incentives feature +## enable storage incentives feature (implied by node-mode: full) # storage-incentives-enable: false ## reserve capacity doubling # reserve-capacity-doubling: 0 diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index 19bc0e3b21a..6da2d4e6ef4 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -2,9 +2,12 @@ ## ── Node mode ──────────────────────────────────────────────────────────────── ## Selects the operational mode of this node. -## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## full - participates in storage and incentives; requires blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable ## light - uploads and downloads only; requires blockchain-rpc -## ultra-light - free-tier downloads only; no blockchain connection needed (default) +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset, the mode is inferred from the deprecated full-node option and +## blockchain-rpc presence (deprecated, removed in v2.11.0). # node-mode: ultra-light ## ── Blockchain / RPC (required for full and light nodes) ───────────────────── @@ -23,7 +26,7 @@ ## ── Swap / chequebook (full and light nodes only) ──────────────────────────── ## enable swap # swap-enable: false -## enable chequebook +## enable chequebook (requires swap-enable; implied by node-mode: full) # chequebook-enable: false ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false @@ -33,7 +36,7 @@ # swap-initial-deposit: "0" ## ── Full node only ──────────────────────────────────────────────────────────── -## enable storage incentives feature +## enable storage incentives feature (implied by node-mode: full) # storage-incentives-enable: false ## reserve capacity doubling # reserve-capacity-doubling: 0 diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index ee2b45a18ed..52eca12ada6 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -2,9 +2,12 @@ ## ── Node mode ──────────────────────────────────────────────────────────────── ## Selects the operational mode of this node. -## full - participates in storage and incentives; requires swap-enable and blockchain-rpc +## full - participates in storage and incentives; requires blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable ## light - uploads and downloads only; requires blockchain-rpc -## ultra-light - free-tier downloads only; no blockchain connection needed (default) +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset, the mode is inferred from the deprecated full-node option and +## blockchain-rpc presence (deprecated, removed in v2.11.0). # node-mode: ultra-light ## ── Blockchain / RPC (required for full and light nodes) ───────────────────── @@ -23,7 +26,7 @@ ## ── Swap / chequebook (full and light nodes only) ──────────────────────────── ## enable swap # swap-enable: false -## enable chequebook +## enable chequebook (requires swap-enable; implied by node-mode: full) # chequebook-enable: false ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false @@ -33,7 +36,7 @@ # swap-initial-deposit: "0" ## ── Full node only ──────────────────────────────────────────────────────────── -## enable storage incentives feature +## enable storage incentives feature (implied by node-mode: full) # storage-incentives-enable: false ## reserve capacity doubling # reserve-capacity-doubling: 0 From d8086ba54ea11d8c821beafc10f1bf6b09832923 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Thu, 3 Sep 2026 14:57:43 +0300 Subject: [PATCH 10/11] ci: point Beekeeper job at the refactor/node-mode-two-regimes companion branch --- .github/workflows/beekeeper.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beekeeper.yml b/.github/workflows/beekeeper.yml index 63c9792c02e..f8e5cf97fea 100644 --- a/.github/workflows/beekeeper.yml +++ b/.github/workflows/beekeeper.yml @@ -20,7 +20,7 @@ env: SETUP_CONTRACT_IMAGE: "ethersphere/bee-localchain" SETUP_CONTRACT_IMAGE_TAG: "0.9.4" BEELOCAL_BRANCH: "main" - BEEKEEPER_BRANCH: "refactor/node-mode-config" + BEEKEEPER_BRANCH: "refactor/node-mode-two-regimes" BEEKEEPER_METRICS_ENABLED: false REACHABILITY_OVERRIDE_PUBLIC: true BATCHFACTOR_OVERRIDE_PUBLIC: 2 From e00782d2cb8935f67927db752f8bb9c16f318b03 Mon Sep 17 00:00:00 2001 From: Calin Martinconi Date: Fri, 11 Sep 2026 11:12:59 +0300 Subject: [PATCH 11/11] fix(cmd): preserve flag defaults and apply mode overrides when node-mode is set --- cmd/bee/cmd/cmd.go | 4 +-- cmd/bee/cmd/start.go | 43 ++++++++++++++++++++++--------- packaging/bee.yaml | 4 +-- packaging/docker/env | 8 +++--- packaging/homebrew-amd64/bee.yaml | 4 +-- packaging/homebrew-arm64/bee.yaml | 4 +-- packaging/scoop/bee.yaml | 4 +-- 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index 23056913c0d..0b2e5aa0d2f 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -360,7 +360,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().String(optionNameSwapInitialDeposit, "0", "initial deposit if deploying a new chequebook") cmd.Flags().String(optionNameNodeMode, "", "node operational mode: full, light, or ultra-light (unset: inferred from deprecated full-node and blockchain-rpc-endpoint)") cmd.Flags().Bool(optionNameSwapEnable, false, "enable swap") - cmd.Flags().Bool(optionNameChequebookEnable, false, "enable chequebook (requires swap-enable)") + cmd.Flags().Bool(optionNameChequebookEnable, true, "enable chequebook") cmd.Flags().Bool(optionNameChequebookVerification, false, "reject full-node hive/handshake records that carry no chequebook address") cmd.Flags().String(optionNameChequebookMinBalance, "110000000000000000", "minimum chequebook token balance required for verification, in token small units (default 11 BZZ)") cmd.Flags().Bool(optionNameFullNode, false, "cause the node to start in full mode (deprecated: use --node-mode=full)") @@ -383,7 +383,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Bool(optionNamePProfMutex, false, "enable pprof mutex profile") cmd.Flags().StringSlice(optionNameStaticNodes, []string{}, "protect nodes from getting kicked out on bootnode") cmd.Flags().Bool(optionNameAllowPrivateCIDRs, false, "allow to advertise private CIDRs to the public network") - cmd.Flags().Bool(optionNameStorageIncentivesEnable, false, "enable storage incentives feature (full node only)") + cmd.Flags().Bool(optionNameStorageIncentivesEnable, true, "enable storage incentives feature") cmd.Flags().Uint64(optionNameStateStoreCacheCapacity, 100_000, "lru memory caching capacity in number of statestore entries") cmd.Flags().String(optionNameTargetNeighborhood, "", "neighborhood to target in binary format (ex: 111111001) for mining the initial overlay") cmd.Flags().String(optionNameNeighborhoodSuggester, "https://api.swarmscan.io/v1/network/neighborhoods/suggestion", "suggester for target neighborhood") diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 2cff7db0fc4..bced39750f2 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -382,17 +382,18 @@ const legacyNodeModeRemovalVersion = "v2.11.0" // two regimes, selected by whether node-mode is set. // // node-mode unset (legacy): the node behaves exactly as releases before -// node-mode existed. chequebook-enable and storage-incentives-enable fall back -// to their former default of true when not set, the mode is inferred from the +// node-mode existed. Flag defaults of true for chequebook-enable and +// storage-incentives-enable remain in effect, the mode is inferred from the // deprecated full-node option and the presence of blockchain-rpc-endpoint, and // no further validation is applied. A deprecation warning names the equivalent // node-mode value and the release in which the inference is removed. // // node-mode set: the mode owns the config. Options the mode implies are // enabled unless the operator explicitly disabled them, options the mode -// cannot support are rejected when explicitly enabled, and -// blockchain-rpc-endpoint is required for light and full nodes. Implied values -// are written back to config so the rest of node startup picks them up. +// cannot support are rejected when explicitly enabled (overriding the pflag +// defaults of true when unset), and blockchain-rpc-endpoint is required for +// light and full nodes. Implied values are written back to config so the rest +// of node startup picks them up. func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { modeStr := c.config.GetString(optionNameNodeMode) if modeStr == "" { @@ -419,6 +420,12 @@ func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { // for them, so implying the options would only cost a chequebook deploy. if !c.config.GetBool(optionNameBootnodeMode) { c.enableImpliedOption(logger, mode, optionNameSwapEnable) + // If swap was explicitly turned off by the operator and chequebook + // was not explicitly configured, turn off chequebook to prevent an + // accidental contradiction with disabled swap. + if !c.config.GetBool(optionNameSwapEnable) && !c.config.IsSet(optionNameChequebookEnable) { + c.config.Set(optionNameChequebookEnable, false) + } if c.config.GetBool(optionNameSwapEnable) { c.enableImpliedOption(logger, mode, optionNameChequebookEnable) } @@ -428,16 +435,30 @@ func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { if rpcEndpoint == "" { return "", errors.New("light node requires blockchain-rpc-endpoint to be set") } - if c.config.GetBool(optionNameStorageIncentivesEnable) { + // Storage incentives are full-node only; override the pflag default of true when unset. + if !c.config.IsSet(optionNameStorageIncentivesEnable) { + c.config.Set(optionNameStorageIncentivesEnable, false) + } else if c.config.GetBool(optionNameStorageIncentivesEnable) { return "", errors.New("light node cannot have storage-incentives-enable set to true") } + // Chequebook requires swap; if swap is off and chequebook was not explicitly configured, override to false. + if !c.config.GetBool(optionNameSwapEnable) && !c.config.IsSet(optionNameChequebookEnable) { + c.config.Set(optionNameChequebookEnable, false) + } case node.UltraLightMode: if c.config.GetBool(optionNameSwapEnable) { return "", errors.New("ultra-light node cannot have swap-enable set to true") } - if c.config.GetBool(optionNameStorageIncentivesEnable) { + // Storage incentives are not supported on ultra-light; override the pflag default of true when unset. + if !c.config.IsSet(optionNameStorageIncentivesEnable) { + c.config.Set(optionNameStorageIncentivesEnable, false) + } else if c.config.GetBool(optionNameStorageIncentivesEnable) { return "", errors.New("ultra-light node cannot have storage-incentives-enable set to true") } + // Chequebook is not supported on ultra-light; override the pflag default of true when unset. + if !c.config.IsSet(optionNameChequebookEnable) { + c.config.Set(optionNameChequebookEnable, false) + } } // chequebook init is gated on swap-enable in NewBee. With node-mode set the @@ -470,11 +491,9 @@ func (c *command) enableImpliedOption(logger log.Logger, mode node.NodeMode, key // backend for every full node and failed at chain init without one, so the // early error changes the message, not the outcome. func (c *command) resolveLegacyNodeMode(logger log.Logger) (node.NodeMode, error) { - // chequebook-enable and storage-incentives-enable used to default to true. - // Restore that for configs that leave them unset so an upgraded node keeps - // its settlement and incentives behaviour. Both stay gated in NewBee - // (chequebook on swap-enable, incentives on full mode), so this cannot start - // anything the previous release would not have started. + // chequebook-enable and storage-incentives-enable default to true. + // Ensure they are populated in config when unset (e.g. bare viper in tests) + // so an upgraded node keeps its settlement and incentives behaviour. for _, key := range []string{optionNameChequebookEnable, optionNameStorageIncentivesEnable} { if !c.config.IsSet(key) { c.config.Set(key, true) diff --git a/packaging/bee.yaml b/packaging/bee.yaml index 0c94069ee94..bb8b599a931 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -27,7 +27,7 @@ ## enable swap # swap-enable: false ## enable chequebook (requires swap-enable; implied by node-mode: full) -# chequebook-enable: false +# chequebook-enable: true ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false ## swap factory addresses @@ -37,7 +37,7 @@ ## ── Full node only ──────────────────────────────────────────────────────────── ## enable storage incentives feature (implied by node-mode: full) -# storage-incentives-enable: false +# storage-incentives-enable: true ## reserve capacity doubling # reserve-capacity-doubling: 0 ## minimum radius storage threshold diff --git a/packaging/docker/env b/packaging/docker/env index cfb51cbbafb..5a5eabcc1bd 100644 --- a/packaging/docker/env +++ b/packaging/docker/env @@ -37,8 +37,8 @@ # BEE_CACHE_CAPACITY=1000000 ## enable forwarded content caching (default true) # BEE_CACHE_RETRIEVAL=true -## enable chequebook; requires swap; implied by BEE_NODE_MODE=full (default false) -# BEE_CHEQUEBOOK_ENABLE=false +## enable chequebook (default true) +# BEE_CHEQUEBOOK_ENABLE=true ## reject full-node hive/handshake records that carry no chequebook address # BEE_CHEQUEBOOK_VERIFICATION=false ## origins with CORS headers enabled (default []) @@ -117,8 +117,8 @@ # BEE_STATIC_NODES=[] ## lru memory caching capacity in number of statestore entries (default 100000) # BEE_STATESTORE_CACHE_CAPACITY=100000 -## enable storage incentives feature; full node only; implied by BEE_NODE_MODE=full (default false) -# BEE_STORAGE_INCENTIVES_ENABLE=false +## enable storage incentives feature (default true) +# BEE_STORAGE_INCENTIVES_ENABLE=true ## enable swap (default false) # BEE_SWAP_ENABLE=false ## swap factory addresses (default empty) diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index 315b58689bd..6700a9b9767 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -27,7 +27,7 @@ ## enable swap # swap-enable: false ## enable chequebook (requires swap-enable; implied by node-mode: full) -# chequebook-enable: false +# chequebook-enable: true ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false ## swap factory addresses @@ -37,7 +37,7 @@ ## ── Full node only ──────────────────────────────────────────────────────────── ## enable storage incentives feature (implied by node-mode: full) -# storage-incentives-enable: false +# storage-incentives-enable: true ## reserve capacity doubling # reserve-capacity-doubling: 0 ## minimum radius storage threshold diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index 6da2d4e6ef4..ddc431faa14 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -27,7 +27,7 @@ ## enable swap # swap-enable: false ## enable chequebook (requires swap-enable; implied by node-mode: full) -# chequebook-enable: false +# chequebook-enable: true ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false ## swap factory addresses @@ -37,7 +37,7 @@ ## ── Full node only ──────────────────────────────────────────────────────────── ## enable storage incentives feature (implied by node-mode: full) -# storage-incentives-enable: false +# storage-incentives-enable: true ## reserve capacity doubling # reserve-capacity-doubling: 0 ## minimum radius storage threshold diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index 52eca12ada6..90e4a2ac989 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -27,7 +27,7 @@ ## enable swap # swap-enable: false ## enable chequebook (requires swap-enable; implied by node-mode: full) -# chequebook-enable: false +# chequebook-enable: true ## reject full-node hive/handshake records that carry no chequebook address # chequebook-verification: false ## swap factory addresses @@ -37,7 +37,7 @@ ## ── Full node only ──────────────────────────────────────────────────────────── ## enable storage incentives feature (implied by node-mode: full) -# storage-incentives-enable: false +# storage-incentives-enable: true ## reserve capacity doubling # reserve-capacity-doubling: 0 ## minimum radius storage threshold