diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index c28ee2a18b7..8352473a616 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -57,11 +57,12 @@ const ( optionNameBzzTokenAddress = "bzz-token-address" optionNameSwapFactoryAddress = "swap-factory-address" optionNameSwapInitialDeposit = "swap-initial-deposit" + optionNameNodeMode = "node-mode" optionNameSwapEnable = "swap-enable" optionNameChequebookEnable = "chequebook-enable" optionNameChequebookVerification = "chequebook-verification" optionNameChequebookMinBalance = "chequebook-min-balance" - optionNameFullNode = "full-node" + optionNameFullNode = "full-node" // Deprecated: use node-mode instead. optionNameLightNodeLimit = "light-node-limit" optionNamePostageContractAddress = "postage-stamp-address" optionNamePostageContractStartBlock = "postage-stamp-start-block" @@ -360,11 +361,13 @@ 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, "", "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, 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") + cmd.Flags().Bool(optionNameFullNode, false, "cause the node to start in full mode (deprecated: use --node-mode=full)") + _ = cmd.Flags().MarkDeprecated(optionNameFullNode, "use --node-mode=full instead") cmd.Flags().Int(optionNameLightNodeLimit, 100, "light node limit") cmd.Flags().String(optionNamePostageContractAddress, "", "postage stamp contract address") cmd.Flags().Uint64(optionNamePostageContractStartBlock, 0, "postage stamp contract start block number") diff --git a/cmd/bee/cmd/export_test.go b/cmd/bee/cmd/export_test.go index 917851c925b..98e0add9e43 100644 --- a/cmd/bee/cmd/export_test.go +++ b/cmd/bee/cmd/export_test.go @@ -7,7 +7,10 @@ package cmd import ( "io" + "github.com/ethersphere/bee/v2/pkg/log" + "github.com/ethersphere/bee/v2/pkg/node" "github.com/spf13/cobra" + "github.com/spf13/viper" ) type ( @@ -89,3 +92,54 @@ func WithPasswordReader(r PasswordReader) func(c *Command) { func (c *Command) SetCfgFileForTest(f string) { c.cfgFile = f } + +// Config keys read by node-mode resolution. +const ( + OptionNameNodeMode = optionNameNodeMode + OptionNameFullNode = optionNameFullNode + OptionNameBootnodeMode = optionNameBootnodeMode + OptionNameSwapEnable = optionNameSwapEnable + OptionNameChequebookEnable = optionNameChequebookEnable + OptionNameStorageIncentivesEnable = optionNameStorageIncentivesEnable + OptionNameChequebookVerification = optionNameChequebookVerification + OptionNameReserveCapacityDoubling = optionReserveCapacityDoubling + ConfigKeyBlockchainRpcEndpoint = configKeyBlockchainRpcEndpoint +) + +// ResolveNodeModeForTest runs node-mode resolution against cfg, leaving the +// resolved option values in cfg. +func ResolveNodeModeForTest(cfg *viper.Viper) (node.NodeMode, error) { + return ResolveNodeModeWithLoggerForTest(cfg, log.Noop) +} + +// ResolveNodeModeWithLoggerForTest is ResolveNodeModeForTest with a logger, for +// tests that assert on the warnings resolution emits. +func ResolveNodeModeWithLoggerForTest(cfg *viper.Viper, logger log.Logger) (node.NodeMode, error) { + c := &command{config: cfg, logger: logger} + return c.resolveNodeMode(c.logger) +} + +// BindStartFlagsForTest mirrors the start command's PreRunE: it binds cfg to +// the command flags, then maps the flat blockchain-rpc-* flags onto their +// nested config keys. +func BindStartFlagsForTest(cfg *viper.Viper, startCmd *cobra.Command) error { + if err := cfg.BindPFlags(startCmd.Flags()); err != nil { + return err + } + c := &command{config: cfg} + c.bindBlockchainRpcConfig(startCmd) + return nil +} + +// LegacyMigrationForTest resolves a config without node-mode through the +// production path and returns the inferred mode with the migration the +// deprecation warning lists. +func LegacyMigrationForTest(cfg *viper.Viper) (mode node.NodeMode, set, remove, notes []string, err error) { + c := &command{config: cfg, logger: log.Noop} + mode, err = c.resolveNodeMode(c.logger) + if err != nil { + return "", nil, nil, nil, err + } + m := c.legacyMigration(mode) + return mode, m.set, m.remove, m.notes, nil +} 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..fc8c4b34517 --- /dev/null +++ b/cmd/bee/cmd/resolve_node_mode_test.go @@ -0,0 +1,982 @@ +// 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_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/ethersphere/bee/v2/cmd/bee/cmd" + "github.com/ethersphere/bee/v2/pkg/log" + "github.com/ethersphere/bee/v2/pkg/node" + "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 + }{ + // ── node-mode set: the mode owns the config ────────────────────────────── + { + name: "full with rpc only implies swap, chequebook and incentives", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + name: "full with all options explicitly enabled succeeds", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + wantMode: node.FullMode, + }, + { + name: "full without rpc is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + }, + wantErr: "full node requires blockchain-rpc-endpoint", + }, + { + // A non-staking full node is a legitimate opt-out. + name: "full with storage-incentives explicitly false honors the opt-out", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + // 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{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + // Receive-only swap: cash out cheques without issuing them. + name: "full with chequebook explicitly false keeps swap on", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameChequebookEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: false, + }, + }, + { + name: "full with chequebook explicitly true and swap explicitly false is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: true, + }, + wantErr: "chequebook-enable requires swap-enable", + }, + { + // NewBee never runs the reserve or the incentives agent for a + // bootnode, so full mode must not imply swap, chequebook or + // incentives there. + name: "full bootnode does not imply swap, chequebook or incentives", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "full bootnode with swap explicitly enabled enables chequebook", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + }, + }, + { + name: "full bootnode with storage-incentives explicitly false succeeds", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + }, + { + name: "light with rpc succeeds without swap", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "light with rpc, swap and chequebook succeeds", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + }, + wantMode: node.LightMode, + }, + { + name: "light without rpc is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + }, + wantErr: "light node requires blockchain-rpc-endpoint", + }, + { + name: "light with chequebook but no swap is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameChequebookEnable: true, + }, + wantErr: "chequebook-enable requires swap-enable", + }, + { + name: "light with swap enables chequebook", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + }, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + }, + }, + { + name: "light rejects storage-incentives-enable", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameStorageIncentivesEnable: true, + }, + wantErr: "light node cannot have storage-incentives-enable", + }, + { + name: "ultra-light succeeds", + config: map[string]any{ + cmd.OptionNameNodeMode: "ultra-light", + }, + wantMode: node.UltraLightMode, + }, + { + name: "ultra-light with rpc ignores rpc and succeeds", + config: map[string]any{ + cmd.OptionNameNodeMode: "ultra-light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.UltraLightMode, + }, + { + name: "ultra-light rejects swap-enable", + config: map[string]any{ + cmd.OptionNameNodeMode: "ultra-light", + cmd.OptionNameSwapEnable: true, + }, + wantErr: "ultra-light node cannot have swap-enable", + }, + { + name: "ultra-light rejects storage-incentives-enable", + config: map[string]any{ + cmd.OptionNameNodeMode: "ultra-light", + cmd.OptionNameStorageIncentivesEnable: true, + }, + wantErr: "ultra-light node cannot have storage-incentives-enable", + }, + { + name: "ultra-light rejects chequebook-enable", + config: map[string]any{ + cmd.OptionNameNodeMode: "ultra-light", + cmd.OptionNameChequebookEnable: true, + }, + wantErr: "ultra-light node cannot have chequebook-enable", + }, + { + name: "node-mode takes precedence over legacy full-node", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.OptionNameFullNode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.LightMode, + }, + { + name: "invalid node-mode value is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "superlight", + }, + wantErr: "invalid node-mode", + }, + { + name: "uppercase node-mode is accepted", + config: map[string]any{ + cmd.OptionNameNodeMode: "FULL", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + }, + { + name: "node-mode surrounded by whitespace is accepted", + config: map[string]any{ + cmd.OptionNameNodeMode: " light ", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.LightMode, + }, + { + name: "whitespace-only node-mode is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: " ", + }, + wantErr: "invalid node-mode", + }, + { + name: "full with whitespace-only rpc is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: " ", + }, + wantErr: "full node requires blockchain-rpc-endpoint", + }, + { + name: "light with whitespace-only rpc is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: " ", + }, + wantErr: "light node requires blockchain-rpc-endpoint", + }, + { + name: "light rejects reserve-capacity-doubling", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameReserveCapacityDoubling: 1, + }, + wantErr: "reserve capacity doubling is only allowed for full nodes", + }, + { + name: "full accepts reserve-capacity-doubling", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameReserveCapacityDoubling: 1, + }, + wantMode: node.FullMode, + }, + { + name: "light rejects chequebook-verification", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookVerification: true, + }, + wantErr: "chequebook-verification requires", + }, + { + name: "full with swap off rejects chequebook-verification", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookVerification: true, + }, + wantErr: "chequebook-verification requires", + }, + { + name: "full accepts chequebook-verification", + config: map[string]any{ + cmd.OptionNameNodeMode: "full", + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameChequebookVerification: true, + }, + wantMode: node.FullMode, + }, + { + name: "light bootnode is rejected", + config: map[string]any{ + cmd.OptionNameNodeMode: "light", + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantErr: "boot node must be started as a full node", + }, + + // ── node-mode unset: legacy behavior, as before node-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 keeps chequebook default", + config: map[string]any{ + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + }, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + // The old shipped default; chequebook stays gated on swap in NewBee. + name: "legacy chequebook without swap starts", + config: map[string]any{ + cmd.OptionNameChequebookEnable: true, + }, + wantMode: node.UltraLightMode, + }, + { + name: "legacy full-node with rpc only keeps old defaults and leaves swap off", + config: map[string]any{ + cmd.OptionNameFullNode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + name: "legacy full-node with all options set maps to full", + config: map[string]any{ + cmd.OptionNameFullNode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + wantMode: node.FullMode, + }, + { + name: "legacy full-node keeps explicit opt-outs", + config: map[string]any{ + cmd.OptionNameFullNode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "legacy bootnode with storage-incentives false starts", + config: map[string]any{ + cmd.OptionNameFullNode: true, + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameStorageIncentivesEnable: false, + }, + wantMode: node.FullMode, + }, + { + // 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 is rejected", + config: map[string]any{ + cmd.OptionNameFullNode: true, + }, + wantErr: "full node requires blockchain-rpc-endpoint", + }, + { + name: "legacy with rpc infers light", + config: map[string]any{ + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantMode: node.LightMode, + }, + { + name: "legacy without rpc infers ultra-light", + config: map[string]any{}, + wantMode: node.UltraLightMode, + }, + { + // Beekeeper's inherited-config scenario: swap-enable inherited from the + // base profile on a node without rpc. Releases before node-mode failed + // it in NewBee; it is now rejected before the node is built. + name: "legacy without rpc but with swap is rejected", + config: map[string]any{ + cmd.OptionNameSwapEnable: true, + }, + wantErr: "swap is enabled but the chain backend is not", + }, + { + name: "legacy light bootnode is rejected", + config: map[string]any{ + cmd.OptionNameBootnodeMode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + }, + wantErr: "boot node must be started as a full node", + }, + { + name: "legacy light rejects reserve-capacity-doubling", + config: map[string]any{ + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameReserveCapacityDoubling: 1, + }, + wantErr: "reserve capacity doubling is only allowed for full nodes", + }, + { + // Before node-mode, a whitespace-only endpoint selected light mode + // and failed at chain init; inference keeps that. + name: "legacy whitespace-only rpc infers light", + config: map[string]any{ + cmd.ConfigKeyBlockchainRpcEndpoint: " ", + }, + wantMode: node.LightMode, + }, + { + name: "legacy full-node with whitespace-only rpc is rejected", + config: map[string]any{ + cmd.OptionNameFullNode: true, + cmd.ConfigKeyBlockchainRpcEndpoint: " ", + }, + wantErr: "full node requires blockchain-rpc-endpoint", + }, + { + // Legacy must not restore a default the operator overrode. + name: "legacy explicit false is preserved", + config: map[string]any{ + cmd.ConfigKeyBlockchainRpcEndpoint: testRPCEndpoint, + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Start from the real start flags, so options a case leaves out + // keep their production defaults. + cfg := boundStartConfig(t, nil) + for k, v := range tt.config { + cfg.Set(k, v) + } + + gotMode, err := cmd.ResolveNodeModeForTest(cfg) + + 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) + } + for key, want := range tt.wantOptions { + if got := cfg.GetBool(key); got != want { + t.Errorf("option %q: got %t, want %t", key, got, want) + } + } + }) + } +} + +// 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() + + tests := []struct { + name string + args []string + wantMode node.NodeMode + wantErr string + wantOptions map[string]bool + }{ + { + name: "no flags selects legacy regime and keeps old defaults", + args: nil, + wantMode: node.UltraLightMode, + wantOptions: map[string]bool{ + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + name: "legacy full-node flag with rpc", + args: []string{"--full-node", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: true, + cmd.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{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: true, + }, + }, + { + name: "node-mode light with rpc leaves swap and chequebook off", + args: []string{"--node-mode=light", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.LightMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "node-mode full bootnode does not inherit chequebook or incentives defaults", + args: []string{"--node-mode=full", "--bootnode-mode", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "node-mode full bootnode with swap enables chequebook", + args: []string{"--node-mode=full", "--bootnode-mode", "--swap-enable", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantMode: node.FullMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "node-mode ultra-light leaves chequebook and incentives off", + args: []string{"--node-mode=ultra-light"}, + wantMode: node.UltraLightMode, + wantOptions: map[string]bool{ + cmd.OptionNameSwapEnable: false, + cmd.OptionNameChequebookEnable: false, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + { + name: "node-mode ultra-light rejects chequebook-enable", + args: []string{"--node-mode=ultra-light", "--chequebook-enable"}, + wantErr: "ultra-light node cannot have chequebook-enable", + }, + { + 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{ + cmd.OptionNameSwapEnable: true, + cmd.OptionNameChequebookEnable: true, + cmd.OptionNameStorageIncentivesEnable: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + cfg := boundStartConfig(t, tt.args) + + gotMode, err := cmd.ResolveNodeModeForTest(cfg) + if tt.wantErr != "" { + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("expected error containing %q, got %v", tt.wantErr, err) + } + return + } + 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 := cfg.GetBool(key); got != want { + t.Errorf("option %q: got %t, want %t", key, got, want) + } + } + }) + } +} + +func TestResolveNodeModeWarnings(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + args []string + // wantErr is the expected resolution error, empty for none. want lists + // substrings the log must contain; absent lists substrings it must not. + wantErr string + want, absent []string + }{ + { + name: "ultra-light with rpc", + args: []string{"--node-mode=ultra-light", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{"blockchain-rpc-endpoint is ignored in ultra-light mode"}, + }, + { + name: "ultra-light without rpc", + args: []string{"--node-mode=ultra-light"}, + absent: []string{"blockchain-rpc-endpoint is ignored"}, + }, + { + name: "light with rpc", + args: []string{"--node-mode=light", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"blockchain-rpc-endpoint is ignored"}, + }, + { + name: "light without swap explains the upload limit", + args: []string{"--node-mode=light", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{"uploads and downloads are limited to the free pseudosettle allowance"}, + }, + { + name: "light with swap does not explain the upload limit", + args: []string{"--node-mode=light", "--swap-enable", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"pseudosettle allowance"}, + }, + { + name: "full without swap does not explain the upload limit", + args: []string{"--node-mode=full", "--swap-enable=false", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"pseudosettle allowance"}, + }, + { + name: "legacy light does not explain the upload limit", + args: []string{"--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"pseudosettle allowance"}, + }, + { + name: "full with swap explicitly disabled reports it at info", + args: []string{"--node-mode=full", "--swap-enable=false", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{`"level"="info"`, `"msg"="option implied by node-mode is explicitly disabled" "node_mode"="full" "option"="swap-enable"`}, + absent: []string{`"level"="warning"`}, + }, + { + name: "full with implied options unset does not report them", + args: []string{"--node-mode=full", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"explicitly disabled"}, + }, + { + name: "full bootnode with storage incentives enabled", + args: []string{"--node-mode=full", "--bootnode-mode", "--storage-incentives-enable", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{"storage-incentives-enable has no effect on a bootnode"}, + }, + { + name: "full bootnode without storage incentives", + args: []string{"--node-mode=full", "--bootnode-mode", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + absent: []string{"has no effect on a bootnode"}, + }, + { + name: "legacy full node lists the migration", + args: []string{"--full-node", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{"node-mode is not set", `"set"=["node-mode=full","swap-enable=false"]`, `"remove"=["full-node"]`}, + }, + { + name: "legacy light node lists options to remove", + args: []string{"--blockchain-rpc-endpoint=" + testRPCEndpoint, "--storage-incentives-enable"}, + want: []string{`"set"=["node-mode=light"]`, `"remove"=["storage-incentives-enable"]`}, + absent: []string{"behavior_change"}, + }, + { + name: "legacy full node with verification notes the swap change", + args: []string{"--full-node", "--chequebook-verification", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + want: []string{`"set"=["node-mode=full"]`, "set swap-enable=false and remove chequebook-verification"}, + }, + { + name: "legacy whitespace-only rpc notes that light needs an endpoint", + args: []string{"--blockchain-rpc-endpoint= "}, + want: []string{`"set"=["node-mode=light"]`, "blockchain-rpc-endpoint contains only whitespace"}, + }, + { + name: "legacy bootnode error is not preceded by the migration warning", + args: []string{"--bootnode-mode", "--blockchain-rpc-endpoint=" + testRPCEndpoint}, + wantErr: "boot node must be started as a full node", + absent: []string{"node-mode is not set"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cfg := boundStartConfig(t, tc.args) + + var buf bytes.Buffer + logger := log.NewLogger("resolve-node-mode-"+t.Name(), log.WithSink(&buf)) + _, err := cmd.ResolveNodeModeWithLoggerForTest(cfg, logger) + switch { + case tc.wantErr == "" && err != nil: + t.Fatalf("unexpected error: %v", err) + case tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)): + t.Fatalf("expected error containing %q, got %v", tc.wantErr, err) + } + for _, w := range tc.want { + if !strings.Contains(buf.String(), w) { + t.Errorf("log does not contain %q; log: %s", w, buf.String()) + } + } + for _, a := range tc.absent { + if strings.Contains(buf.String(), a) { + t.Errorf("log contains %q; log: %s", a, buf.String()) + } + } + }) + } +} + +// TestLegacyMigrationPreservesBehavior applies the migration listed by the +// deprecation warning to every legacy combination of the mode-related options +// and checks that the node then runs the same services with node-mode set. +// Where behavior cannot be preserved, the warning must say so. Combinations +// rejected before migration must be exactly those releases before node-mode +// rejected. +// +// The chequebook is modeled as running, which needs swap. The API's +// chequebookEnabled flag reports the option itself and does change for nodes +// without swap: chequebook-enable follows swap-enable once node-mode is set. +func TestLegacyMigrationPreservesBehavior(t *testing.T) { + t.Parallel() + + tri := []string{"", "true", "false"} + type effect struct { + mode node.NodeMode + swap, chequebook, incentives, verification bool + } + effectOf := func(cfg *viper.Viper, mode node.NodeMode, bootnode bool) effect { + swap := cfg.GetBool(cmd.OptionNameSwapEnable) + return effect{ + mode: mode, + swap: swap, + chequebook: swap && cfg.GetBool(cmd.OptionNameChequebookEnable), + incentives: mode == node.FullMode && !bootnode && cfg.GetBool(cmd.OptionNameStorageIncentivesEnable), + verification: cfg.GetBool(cmd.OptionNameChequebookVerification), + } + } + + var checked, rejected, changed int + for _, bootnode := range []bool{false, true} { + for _, rpc := range []bool{false, true} { + for _, fullNode := range tri { + for _, swap := range tri { + for _, chequebook := range tri { + for _, incentives := range tri { + for _, verification := range []string{"", "true"} { + var args []string + add := func(name, value string) { + if value != "" { + args = append(args, "--"+name+"="+value) + } + } + if bootnode { + add(cmd.OptionNameBootnodeMode, "true") + } + if rpc { + add("blockchain-rpc-endpoint", testRPCEndpoint) + } + add(cmd.OptionNameFullNode, fullNode) + add(cmd.OptionNameSwapEnable, swap) + add(cmd.OptionNameChequebookEnable, chequebook) + add(cmd.OptionNameStorageIncentivesEnable, incentives) + add(cmd.OptionNameChequebookVerification, verification) + + // Rules of releases before node-mode: a bootnode must be + // full, a full node needs a chain, swap needs a chain, + // and verification needs a full node with chequebook and + // chain. chequebook-enable defaults to true. + full := fullNode == "true" + chain := full || rpc + wantRejected := (bootnode && !full) || (full && !rpc) || (swap == "true" && !chain) || + (verification == "true" && (!full || chequebook == "false" || !chain)) + + legacyCfg := boundStartConfig(t, args) + mode, set, remove, notes, err := cmd.LegacyMigrationForTest(legacyCfg) + if (err != nil) != wantRejected { + t.Errorf("%v: rejected=%t, want %t (err: %v)", args, err != nil, wantRejected, err) + continue + } + if err != nil { + rejected++ + continue + } + checked++ + before := effectOf(legacyCfg, mode, bootnode) + + var migrated []string + next: + for _, a := range args { + for _, r := range remove { + if strings.HasPrefix(a, "--"+r+"=") { + continue next + } + } + migrated = append(migrated, a) + } + for _, kv := range set { + k, v, _ := strings.Cut(kv, "=") + migrated = append(migrated, "--"+k+"="+v) + } + + migratedCfg := boundStartConfig(t, migrated) + gotMode, err := cmd.ResolveNodeModeForTest(migratedCfg) + if len(notes) > 0 { + changed++ + if err == nil && effectOf(migratedCfg, gotMode, bootnode) == before { + t.Errorf("%v: warning notes a behavior change %v, but the migrated config %v behaves the same", args, notes, migrated) + } + continue + } + if err != nil { + t.Errorf("%v: migrated config %v fails: %v", args, migrated, err) + continue + } + if after := effectOf(migratedCfg, gotMode, bootnode); after != before { + t.Errorf("%v: migrated config %v changes behavior: before %+v, after %+v", args, migrated, before, after) + } + } + } + } + } + } + } + } + if checked == 0 { + t.Fatal("no legacy configuration was checked") + } + t.Logf("checked %d legacy configurations (%d with a noted behavior change), %d rejected as before node-mode", checked, changed, rejected) +} + +func TestResolveNodeModeTrimsRPCEndpoint(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + args []string + want string + }{ + {name: "node-mode set trims the endpoint", args: []string{"--node-mode=full", "--blockchain-rpc-endpoint= " + testRPCEndpoint + " "}, want: testRPCEndpoint}, + {name: "legacy keeps the endpoint as configured", args: []string{"--full-node", "--blockchain-rpc-endpoint= " + testRPCEndpoint + " "}, want: " " + testRPCEndpoint + " "}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cfg := boundStartConfig(t, tc.args) + if _, err := cmd.ResolveNodeModeForTest(cfg); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got := cfg.GetString(cmd.ConfigKeyBlockchainRpcEndpoint); got != tc.want { + t.Errorf("endpoint: got %q, want %q", got, tc.want) + } + }) + } +} + +// boundStartConfig returns a viper bound to the real start command flags +// parsed from args, as production does. +func boundStartConfig(t *testing.T, args []string) *viper.Viper { + t.Helper() + + root, err := cmd.NewCommand(cmd.WithHomeDir(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(args); err != nil { + t.Fatal(err) + } + cfg := viper.New() + if err := cmd.BindStartFlagsForTest(cfg, startCmd); err != nil { + t.Fatal(err) + } + return cfg +} diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 12b82d6b744..e42ba3791a4 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -16,6 +16,7 @@ import ( "os/signal" "path/filepath" "runtime" + "strings" "sync/atomic" "syscall" "time" @@ -62,6 +63,14 @@ func (c *command) initStartCmd() (err error) { fmt.Print(beeWelcomeMessage) logger.Info("bee version", "version", bee.Version) + // Resolve the node mode before the node is built, so that an invalid + // configuration exits with a non-zero status, and before the keystore + // is unlocked. + nodeMode, err := c.resolveNodeMode(logger) + if err != nil { + return err + } + // ctx is global context of bee node; which is canceled after interrupt signal is received. ctx, cancel := context.WithCancel(context.Background()) sysInterruptChannel := make(chan os.Signal, 1) @@ -79,7 +88,7 @@ func (c *command) initStartCmd() (err error) { // Building bee node can take up some time (because node.NewBee(...) is compute have function ) // Because of this we need to do it in background so that program could be terminated when interrupt signal is received // while bee node is being constructed. - respC := buildBeeNodeAsync(ctx, c, cmd, logger) + respC := buildBeeNodeAsync(ctx, c, cmd, logger, nodeMode) var beeNode atomic.Value p := &program{ @@ -180,18 +189,18 @@ type buildBeeNodeResp struct { err error } -func buildBeeNodeAsync(ctx context.Context, c *command, cmd *cobra.Command, logger log.Logger) <-chan buildBeeNodeResp { +func buildBeeNodeAsync(ctx context.Context, c *command, cmd *cobra.Command, logger log.Logger, nodeMode node.NodeMode) <-chan buildBeeNodeResp { respC := make(chan buildBeeNodeResp, 1) go func() { - bee, err := buildBeeNode(ctx, c, cmd, logger) + bee, err := buildBeeNode(ctx, c, cmd, logger, nodeMode) respC <- buildBeeNodeResp{bee, err} }() return respC } -func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger log.Logger) (*node.Bee, error) { +func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger log.Logger, nodeMode node.NodeMode) (*node.Bee, error) { var err error // If the resolver is specified, resolve all connection strings @@ -211,11 +220,6 @@ 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 { - return nil, errors.New("boot node must be started as a full node") - } mainnet := c.config.GetBool(optionNameMainNet) userHasSetNetworkID := c.config.IsSet(optionNameNetworkID) @@ -324,7 +328,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, LightNodeLimit: c.config.GetInt(optionNameLightNodeLimit), Logger: logger, MinimumGasTipCap: c.config.GetUint64(optionNameMinimumGasTipCap), @@ -370,6 +374,270 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo return b, err } +// 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 as releases before node-mode +// existed. The mode is inferred from the deprecated full-node option and the +// presence of blockchain-rpc-endpoint, and the flag defaults of true for +// chequebook-enable and storage-incentives-enable stay in effect. A deprecation +// warning lists the config changes that keep the node's behavior once +// node-mode is set, 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 (full bootnodes are +// exempt), options the mode 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. An unset +// chequebook-enable follows swap-enable. Implied values are written back to +// config so the rest of node startup picks them up. +// +// In both regimes, options the resolved mode cannot run with are rejected +// before the node is built. +func (c *command) resolveNodeMode(logger log.Logger) (node.NodeMode, error) { + raw := c.config.GetString(optionNameNodeMode) + legacy := raw == "" + + var ( + mode node.NodeMode + err error + ) + if legacy { + mode, err = c.resolveLegacyNodeMode() + } else { + mode, err = c.resolveExplicitNodeMode(logger, raw) + } + if err != nil { + return "", err + } + if err := c.validateNodeModeOptions(mode); err != nil { + return "", err + } + if legacy { + c.logLegacyNodeModeMigration(logger, mode) + } + return mode, nil +} + +// resolveExplicitNodeMode applies the regime in which node-mode is set. The +// value is matched case-insensitively, ignoring surrounding whitespace. +func (c *command) resolveExplicitNodeMode(logger log.Logger, raw string) (node.NodeMode, error) { + mode := node.NodeMode(strings.ToLower(strings.TrimSpace(raw))) + if !mode.IsValid() { + return "", fmt.Errorf("invalid node-mode %q: must be one of full, light, ultra-light", raw) + } + if c.config.GetBool(optionNameFullNode) { + logger.Warning("--full-node is set alongside --node-mode; --full-node is ignored") + } + + // Surrounding whitespace is not significant: a whitespace-only endpoint + // counts as unset, and the trimmed value is what the node dials. + rpcEndpoint := strings.TrimSpace(c.config.GetString(configKeyBlockchainRpcEndpoint)) + if rpcEndpoint != c.config.GetString(configKeyBlockchainRpcEndpoint) { + c.config.Set(configKeyBlockchainRpcEndpoint, rpcEndpoint) + } + + switch mode { + case node.FullMode: + if rpcEndpoint == "" { + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") + } + // A full node implies swap, chequebook and storage incentives. Bootnodes + // are exempt: NewBee never runs the reserve or the incentives agent for + // them, so they have no use for the implied options. Swap still starts on + // a bootnode when explicitly enabled, and chequebook follows it. + 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) + } else if !c.config.IsSet(optionNameStorageIncentivesEnable) { + c.config.Set(optionNameStorageIncentivesEnable, false) + } else if c.config.GetBool(optionNameStorageIncentivesEnable) { + logger.Warning("storage-incentives-enable has no effect on a bootnode", "node_mode", mode) + } + case node.LightMode: + if rpcEndpoint == "" { + return "", errors.New("light node requires blockchain-rpc-endpoint to be set") + } + // 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") + } + // Light mode does not imply swap, as before node-mode. Without it the + // node pays peers only through the free pseudosettle allowance, which + // limits uploads and downloads. + if !c.config.GetBool(optionNameSwapEnable) { + logger.Info("swap is disabled, so uploads and downloads are limited to the free pseudosettle allowance; to exceed it set swap-enable=true and fund the chequebook", "node_mode", mode) + } + case node.UltraLightMode: + if rpcEndpoint != "" { + logger.Warning("blockchain-rpc-endpoint is ignored in ultra-light mode", "node_mode", mode) + } + if c.config.GetBool(optionNameSwapEnable) { + return "", errors.New("ultra-light node cannot have swap-enable set to true") + } + // 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") + } + if c.config.IsSet(optionNameChequebookEnable) && c.config.GetBool(optionNameChequebookEnable) { + return "", errors.New("ultra-light node cannot have chequebook-enable set to true") + } + } + + // Chequebook requires swap. When chequebook is not explicitly configured, + // follow swap rather than the pflag default of true. + if !c.config.IsSet(optionNameChequebookEnable) { + c.config.Set(optionNameChequebookEnable, c.config.GetBool(optionNameSwapEnable)) + } + + // 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 honored and reported at Info: +// it is a deliberate choice (the legacy migration may suggest it), not a +// problem for the operator to act on. +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.Info("option implied by node-mode is explicitly disabled", "node_mode", mode, "option", key) + } +} + +// resolveLegacyNodeMode infers the mode as releases before node-mode did, for +// configs that do not set node-mode. A full node must have 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() (node.NodeMode, error) { + switch { + case c.config.GetBool(optionNameFullNode): + if strings.TrimSpace(c.config.GetString(configKeyBlockchainRpcEndpoint)) == "" { + return "", errors.New("full node requires blockchain-rpc-endpoint to be set") + } + return node.FullMode, nil + case c.config.GetString(configKeyBlockchainRpcEndpoint) != "": + // Untrimmed, as before node-mode existed: a whitespace-only endpoint + // still selects light mode and fails at chain init instead of silently + // starting an ultra-light node. + return node.LightMode, nil + default: + return node.UltraLightMode, nil + } +} + +// validateNodeModeOptions rejects options the resolved mode cannot run with, in +// both regimes. Releases before node-mode rejected the same configurations, +// the bootnode one in the start command and the others in NewBee, which keeps +// its checks for other callers. Checking here rejects the configuration before +// the node is built, with a non-zero exit status. +func (c *command) validateNodeModeOptions(mode node.NodeMode) error { + chainEnabled := mode == node.FullMode || mode == node.LightMode + switch { + case c.config.GetBool(optionNameBootnodeMode) && mode != node.FullMode: + return errors.New("boot node must be started as a full node") + case mode != node.FullMode && c.config.GetInt(optionReserveCapacityDoubling) != 0: + return errors.New("reserve capacity doubling is only allowed for full nodes") + case c.config.GetBool(optionNameSwapEnable) && !chainEnabled: + return errors.New("swap is enabled but the chain backend is not; provide --blockchain-rpc-endpoint or disable swap") + case c.config.GetBool(optionNameChequebookVerification) && (mode != node.FullMode || !c.config.GetBool(optionNameChequebookEnable) || !chainEnabled): + return fmt.Errorf("chequebook-verification requires full-node mode, chequebook-enable, and an enabled chain backend (full_node=%t, chequebook_enable=%t, chain_enabled=%t)", + mode == node.FullMode, c.config.GetBool(optionNameChequebookEnable), chainEnabled) + } + return nil +} + +// legacyNodeModeMigration lists the config changes that make a legacy config +// behave the same once node-mode is set to the inferred mode, as option=value +// pairs to set and options to remove. Notes describe behavior that cannot be +// preserved. +type legacyNodeModeMigration struct { + set []string + remove []string + notes []string +} + +func (c *command) legacyMigration(mode node.NodeMode) legacyNodeModeMigration { + var m legacyNodeModeMigration + bootnode := c.config.GetBool(optionNameBootnodeMode) + swap := c.config.GetBool(optionNameSwapEnable) + verification := c.config.GetBool(optionNameChequebookVerification) + + m.set = append(m.set, optionNameNodeMode+"="+string(mode)) + if c.config.IsSet(optionNameFullNode) { + m.remove = append(m.remove, optionNameFullNode) + } + // With node-mode set, chequebook-enable follows swap-enable and + // chequebook-verification requires the chequebook, so verification cannot + // keep running without swap. + switch { + case verification && !swap && mode == node.FullMode && !bootnode && !c.config.IsSet(optionNameSwapEnable): + m.notes = append(m.notes, "node-mode: full enables swap and chequebook, which chequebook-verification requires; to keep swap off, set swap-enable=false and remove chequebook-verification") + case verification && !swap: + m.notes = append(m.notes, "chequebook-verification requires swap-enable once node-mode is set; set swap-enable=true or remove chequebook-verification") + case mode == node.FullMode && !bootnode && !c.config.IsSet(optionNameSwapEnable): + // node-mode: full implies swap, which legacy full nodes did not. + m.set = append(m.set, optionNameSwapEnable+"=false") + } + // With node-mode set, an explicit chequebook-enable=true is rejected + // without swap, and on ultra-light nodes. + if c.config.IsSet(optionNameChequebookEnable) && c.config.GetBool(optionNameChequebookEnable) && (mode == node.UltraLightMode || !swap) { + m.remove = append(m.remove, optionNameChequebookEnable) + } + // Legacy inference treats a whitespace-only endpoint as set; with node-mode + // set it counts as unset, which light mode rejects. + if mode == node.LightMode && strings.TrimSpace(c.config.GetString(configKeyBlockchainRpcEndpoint)) == "" { + m.notes = append(m.notes, "blockchain-rpc-endpoint contains only whitespace; node-mode=light requires an endpoint") + } + // Storage incentives only run on full nodes that are not bootnodes; with + // node-mode set an explicit true is rejected on light and ultra-light nodes. + if c.config.IsSet(optionNameStorageIncentivesEnable) && c.config.GetBool(optionNameStorageIncentivesEnable) && (mode != node.FullMode || bootnode) { + m.remove = append(m.remove, optionNameStorageIncentivesEnable) + } + return m +} + +// logLegacyNodeModeMigration warns that the mode was inferred and lists the +// config changes that keep the node's behavior once node-mode is set. +func (c *command) logLegacyNodeModeMigration(logger log.Logger, mode node.NodeMode) { + m := c.legacyMigration(mode) + kv := []any{ + "node_mode", mode, + "removed_in", legacyNodeModeRemovalVersion, + "set", m.set, + } + if len(m.remove) > 0 { + kv = append(kv, "remove", m.remove) + } + if len(m.notes) > 0 { + kv = append(kv, "behavior_change", m.notes) + } + logger.Warning("node-mode is not set and was inferred from legacy options, which will be removed; to keep the current behavior, apply the listed options in the config file, flags or BEE_* environment variables", kv...) +} + type program struct { start func() stop func() diff --git a/packaging/bee.yaml b/packaging/bee.yaml index 4c91efae60d..53265ac6d07 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -1,13 +1,18 @@ ## 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 blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable +## (bootnodes are exempt: swap only when set, chequebook follows swap) +## light - uploads and downloads only; requires blockchain-rpc; beyond the +## free allowance also needs swap-enable and a funded chequebook +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset (the default), the mode is inferred from the deprecated full-node +## option and blockchain-rpc presence (deprecated, removed in v2.11.0). +# node-mode: "" + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,26 +20,102 @@ # 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 (requires swap-enable; when node-mode is set and this is unset, follows swap-enable) +# chequebook-enable: true +## reject full-node hive/handshake records that carry no chequebook address +# chequebook-verification: 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 (implied by node-mode: full) +# storage-incentives-enable: true +## 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 +## 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" ## bzz token contract address # bzz-token-address: "" ## 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 -## reject full-node hive/handshake records that carry no chequebook address -# chequebook-verification: false -## config file (default is $HOME/.bee.yaml) -config: "/etc/bee/bee.yaml" -## origins with CORS headers enabled -# cors-allowed-origins: [] -## data directory -data-dir: "/var/lib/bee" +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## maximum number of blocks per eth_getLogs request when syncing postage contract events +# postage-sync-block-range: 1000 +## 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 @@ -43,74 +124,39 @@ 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 900000) +# gas-limit-fallback: 900000 +## 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" -## maximum number of blocks per eth_getLogs request when syncing postage contract events -# postage-sync-block-range: 1000 -## 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 +## add caller file:line to log lines (development only) +# log-caller: false ## tracing settings # tracing: # ## enable tracing @@ -127,32 +173,16 @@ password-file: "/var/lib/bee/password" # sampling-ratio: 1.0 # ## service name identifier for tracing # service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 900000) -# gas-limit-fallback: 900000 -## 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 -## add caller file:line to log lines (development only) -# log-caller: false +## 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: "" ## SIMD BMT hashing opt-in flag (only available on linux amd64) # use-simd-hashing: false ## limit of possible connected light nodes (default: 100) 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 830c0316e02..b458860861d 100644 --- a/packaging/docker/docker-compose.yml +++ b/packaging/docker/docker-compose.yml @@ -40,6 +40,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 87043ca4f39..15675fac800 100644 --- a/packaging/docker/env +++ b/packaging/docker/env @@ -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) (default unset) +# BEE_NODE_MODE= ## P2P listen address (default :1634) # BEE_P2P_ADDR=:1634 ## enable P2P WebSocket transport (default false) diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index f343dec9458..32413c6c047 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -1,13 +1,18 @@ ## 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 blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable +## (bootnodes are exempt: swap only when set, chequebook follows swap) +## light - uploads and downloads only; requires blockchain-rpc; beyond the +## free allowance also needs swap-enable and a funded chequebook +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset (the default), the mode is inferred from the deprecated full-node +## option and blockchain-rpc presence (deprecated, removed in v2.11.0). +# node-mode: "" + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,26 +20,102 @@ # 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 (requires swap-enable; when node-mode is set and this is unset, follows swap-enable) +# chequebook-enable: true +## reject full-node hive/handshake records that carry no chequebook address +# chequebook-verification: 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 (implied by node-mode: full) +# storage-incentives-enable: true +## 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 +## 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" ## bzz token contract address # bzz-token-address: "" ## 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 -## reject full-node hive/handshake records that carry no chequebook address -# chequebook-verification: false -## config file (default is $HOME/.bee.yaml) -config: "/usr/local/etc/swarm-bee/bee.yaml" -## origins with CORS headers enabled -# cors-allowed-origins: [] -## data directory -data-dir: "/usr/local/var/lib/swarm-bee" +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## maximum number of blocks per eth_getLogs request when syncing postage contract events +# postage-sync-block-range: 1000 +## 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 @@ -43,74 +124,39 @@ 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 900000) +# gas-limit-fallback: 900000 +## 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" -## maximum number of blocks per eth_getLogs request when syncing postage contract events -# postage-sync-block-range: 1000 -## 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 +## add caller file:line to log lines (development only) +# log-caller: false ## tracing settings # tracing: # ## enable tracing @@ -127,32 +173,16 @@ password-file: "/usr/local/var/lib/swarm-bee/password" # sampling-ratio: 1.0 # ## service name identifier for tracing # service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 900000) -# gas-limit-fallback: 900000 -## 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 -## add caller file:line to log lines (development only) -# log-caller: false +## 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: "" ## SIMD BMT hashing opt-in flag (only available on linux amd64) # use-simd-hashing: false ## limit of possible connected light nodes (default: 100) diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index 4bf3e8e117e..eeeff612f5f 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -1,13 +1,18 @@ ## 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 blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable +## (bootnodes are exempt: swap only when set, chequebook follows swap) +## light - uploads and downloads only; requires blockchain-rpc; beyond the +## free allowance also needs swap-enable and a funded chequebook +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset (the default), the mode is inferred from the deprecated full-node +## option and blockchain-rpc presence (deprecated, removed in v2.11.0). +# node-mode: "" + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,26 +20,102 @@ # 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 (requires swap-enable; when node-mode is set and this is unset, follows swap-enable) +# chequebook-enable: true +## reject full-node hive/handshake records that carry no chequebook address +# chequebook-verification: 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 (implied by node-mode: full) +# storage-incentives-enable: true +## 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 +## 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" ## bzz token contract address # bzz-token-address: "" ## 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 -## reject full-node hive/handshake records that carry no chequebook address -# chequebook-verification: false -## config file (default is $HOME/.bee.yaml) -config: "/opt/homebrew/etc/swarm-bee/bee.yaml" -## origins with CORS headers enabled -# cors-allowed-origins: [] -## data directory -data-dir: "/opt/homebrew/var/lib/swarm-bee" +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## maximum number of blocks per eth_getLogs request when syncing postage contract events +# postage-sync-block-range: 1000 +## 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 @@ -43,74 +124,39 @@ 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 900000) +# gas-limit-fallback: 900000 +## 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" -## maximum number of blocks per eth_getLogs request when syncing postage contract events -# postage-sync-block-range: 1000 -## 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 +## add caller file:line to log lines (development only) +# log-caller: false ## tracing settings # tracing: # ## enable tracing @@ -127,32 +173,16 @@ password-file: "/opt/homebrew/var/lib/swarm-bee/password" # sampling-ratio: 1.0 # ## service name identifier for tracing # service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 900000) -# gas-limit-fallback: 900000 -## 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 -## add caller file:line to log lines (development only) -# log-caller: false +## 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: "" ## SIMD BMT hashing opt-in flag (only available on linux amd64) # use-simd-hashing: false ## limit of possible connected light nodes (default: 100) diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index 2b5f6a94db0..834a1cf3826 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -1,13 +1,18 @@ ## 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 blockchain-rpc; +## implies swap-enable, chequebook-enable and storage-incentives-enable +## (bootnodes are exempt: swap only when set, chequebook follows swap) +## light - uploads and downloads only; requires blockchain-rpc; beyond the +## free allowance also needs swap-enable and a funded chequebook +## ultra-light - free-tier downloads only; no blockchain connection needed +## When unset (the default), the mode is inferred from the deprecated full-node +## option and blockchain-rpc presence (deprecated, removed in v2.11.0). +# node-mode: "" + +## ── Blockchain / RPC (required for full and light nodes) ───────────────────── ## blockchain rpc configuration # blockchain-rpc: # endpoint: "" @@ -15,26 +20,102 @@ # 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 (requires swap-enable; when node-mode is set and this is unset, follows swap-enable) +# chequebook-enable: true +## reject full-node hive/handshake records that carry no chequebook address +# chequebook-verification: 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 (implied by node-mode: full) +# storage-incentives-enable: true +## 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 +## 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" ## bzz token contract address # bzz-token-address: "" ## 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 -## reject full-node hive/handshake records that carry no chequebook address -# chequebook-verification: false -## config file (default is $HOME/.bee.yaml) -config: "./bee.yaml" -## origins with CORS headers enabled -# cors-allowed-origins: [] -## data directory -data-dir: "./data" +## postage stamp contract address +# postage-stamp-address: "" +## postage stamp contract start block number +# postage-stamp-start-block: "0" +## maximum number of blocks per eth_getLogs request when syncing postage contract events +# postage-sync-block-range: 1000 +## 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 @@ -43,74 +124,39 @@ 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 900000) +# gas-limit-fallback: 900000 +## 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" -## maximum number of blocks per eth_getLogs request when syncing postage contract events -# postage-sync-block-range: 1000 -## 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 +## add caller file:line to log lines (development only) +# log-caller: false ## tracing settings # tracing: # ## enable tracing @@ -127,32 +173,16 @@ password-file: "./password" # sampling-ratio: 1.0 # ## service name identifier for tracing # service-name: bee -## gas limit fallback when estimation fails for contract transactions (default 900000) -# gas-limit-fallback: 900000 -## 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 -## add caller file:line to log lines (development only) -# log-caller: false +## 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: "" ## SIMD BMT hashing opt-in flag (only available on linux amd64) # use-simd-hashing: false ## limit of possible connected light nodes (default: 100) diff --git a/pkg/node/export_test.go b/pkg/node/export_test.go index 5823e14c79d..9d6443059f9 100644 --- a/pkg/node/export_test.go +++ b/pkg/node/export_test.go @@ -13,6 +13,7 @@ import ( var ( ValidatePublicAddress = validatePublicAddress UseEmbeddedSnapshot = useEmbeddedSnapshot + IsChainEnabled = isChainEnabled ) // NewTestBeeWithClosers builds a Bee with only the push-sync and retrieval diff --git a/pkg/node/node.go b/pkg/node/node.go index b9a93612e3e..ffdfaa922fc 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -132,6 +132,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 @@ -166,7 +183,7 @@ type Options struct { EnableWS bool AutoTLSDomain string AutoTLSRegistrationEndpoint string - FullNodeMode bool + NodeMode NodeMode LightNodeLimit int GasLimitFallback uint64 Logger log.Logger @@ -278,7 +295,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 } @@ -303,7 +320,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") } @@ -426,14 +443,14 @@ func NewBee( erc20Service erc20.Service ) - chainEnabled := isChainEnabled(o, o.BlockchainRpcEndpoint, logger) + chainEnabled := isChainEnabled(o, logger) if o.SwapEnable && !chainEnabled { return nil, errors.New("swap is enabled but the chain backend is not; provide --blockchain-rpc-endpoint or disable swap") } - if o.ChequebookVerification && (!o.FullNodeMode || !o.ChequebookEnable || !chainEnabled) { - return nil, fmt.Errorf("chequebook-verification requires full-node mode, chequebook-enable, and an enabled chain backend (full_node=%t, chequebook_enable=%t, chain_enabled=%t)", o.FullNodeMode, o.ChequebookEnable, chainEnabled) + if o.ChequebookVerification && (o.NodeMode != FullMode || !o.ChequebookEnable || !chainEnabled) { + return nil, fmt.Errorf("chequebook-verification requires full-node mode, chequebook-enable, and an enabled chain backend (full_node=%t, chequebook_enable=%t, chain_enabled=%t)", o.NodeMode == FullMode, o.ChequebookEnable, chainEnabled) } var batchStore postage.Storer = new(postage.NoOpBatchStore) @@ -482,10 +499,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 } @@ -737,7 +757,7 @@ func NewBee( AutoTLSRegistrationEndpoint: o.AutoTLSRegistrationEndpoint, AutoTLSCAEndpoint: o.AutoTLSCAEndpoint, WelcomeMessage: o.WelcomeMessage, - FullNode: o.FullNodeMode, + FullNode: o.NodeMode == FullMode, LightNodeLimit: o.LightNodeLimit, Nonce: nonce, AllowPrivateCIDRs: o.AllowPrivateCIDRs, @@ -871,7 +891,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 @@ -986,7 +1006,7 @@ func NewBee( } } - if o.FullNodeMode { + if o.NodeMode == FullMode { err = batchSvc.Start(ctx, postageSyncStart) syncStatus.Store(true) if err != nil { @@ -1011,7 +1031,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) } @@ -1044,7 +1064,7 @@ func NewBee( var enforcedRefreshRate *big.Int - if o.FullNodeMode { + if o.NodeMode == FullMode { enforcedRefreshRate = big.NewInt(refreshRate) } else { enforcedRefreshRate = big.NewInt(lightRefreshRate) @@ -1139,7 +1159,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(): @@ -1166,7 +1186,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 @@ -1190,7 +1210,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 { @@ -1275,7 +1295,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 @@ -1614,21 +1634,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 != FullMode && o.NodeMode != LightMode { + 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 { diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go index c99c4f1fc2d..7ce1e133fad 100644 --- a/pkg/node/node_test.go +++ b/pkg/node/node_test.go @@ -191,6 +191,25 @@ func TestShutdownRegistersPushSyncAndRetrieval(t *testing.T) { } } +func TestIsChainEnabled(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + mode node.NodeMode + want bool + }{ + {mode: node.FullMode, want: true}, + {mode: node.LightMode, want: true}, + {mode: node.UltraLightMode, want: false}, + {mode: "", want: false}, + {mode: "unknown", want: false}, + } { + if got := node.IsChainEnabled(&node.Options{NodeMode: tc.mode}, log.Noop); got != tc.want { + t.Errorf("node mode %q: got %t, want %t", tc.mode, got, tc.want) + } + } +} + // orderedCloser records its name in order when closed and returns err. type orderedCloser struct { name string