Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/beekeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment thread
martinconic marked this conversation as resolved.
BEEKEEPER_METRICS_ENABLED: false
REACHABILITY_OVERRIDE_PUBLIC: true
BATCHFACTOR_OVERRIDE_PUBLIC: 2
Expand Down
13 changes: 9 additions & 4 deletions cmd/bee/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure how this change will affect old users ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe some tests could help for those changes ?

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")
Expand All @@ -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")
Expand Down
156 changes: 156 additions & 0 deletions cmd/bee/cmd/resolve_node_mode_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
58 changes: 55 additions & 3 deletions cmd/bee/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -337,6 +341,54 @@ 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.
// 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)

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)
}
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
}

// 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
}

// Infer light vs ultra-light from RPC endpoint presence (original behaviour).
if rpcEndpoint != "" {
return node.LightMode, nil
}
return node.UltraLightMode, nil
}

type program struct {
start func()
stop func()
Expand Down
Loading
Loading