diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f15e487d..c2288848 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,9 +28,9 @@ jobs: - name: Lint uses: golangci/golangci-lint-action@v9 with: - version: v2.10.1 + version: v2.11.3 args: --timeout 10m - skip-cache: false + skip-cache: true - name: Vet run: make vet - name: Whitespace check diff --git a/AGENTS.md b/AGENTS.md index 6b281c28..3ce51c5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ Beekeeper consumes Bee as a library (`github.com/ethersphere/bee/v2` in `go.mod` ```bash make binary # build ./dist/beekeeper (CGO disabled, version stamped via -ldflags) make test # unit tests: go test -v ./pkg/... -make lint # golangci-lint (pinned v2.10.1; auto-installs if missing) +make lint # golangci-lint (pinned v2.11.3; auto-installs if missing) ``` Run a single test: `go test -run TestName ./pkg//...` (add `-race` to mirror CI). diff --git a/Makefile b/Makefile index 86744fd9..4d6ca66e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ GO ?= go GOLANGCI_LINT ?= golangci-lint -GOLANGCI_LINT_VERSION ?= v2.10.1 +GOLANGCI_LINT_VERSION ?= v2.11.3 COMMIT ?= "$(shell git describe --long --dirty --always --match "" || true)" VERSION ?= "$(shell git describe --tags --abbrev=0 | cut -c2-)" LDFLAGS ?= -s -w \ diff --git a/config/config.yaml b/config/config.yaml index 5a806628..dcd87ded 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -104,7 +104,7 @@ bee-configs: db-disable-seeks-compaction: false db-open-files-limit: 200 db-write-buffer-size: 33554432 - full-node: true + node-mode: full mainnet: false nat-addr: "" network-id: 12345 diff --git a/config/light-node.yaml b/config/light-node.yaml index 3722e979..2b5e6786 100644 --- a/config/light-node.yaml +++ b/config/light-node.yaml @@ -2,7 +2,7 @@ bee-configs: light-node: _inherit: default - full-node: false + node-mode: light # node groups for light nodes node-groups: diff --git a/config/local.yaml b/config/local.yaml index d389ee2b..f8be852a 100644 --- a/config/local.yaml +++ b/config/local.yaml @@ -162,7 +162,7 @@ bee-configs: db-disable-seeks-compaction: false db-open-files-limit: 200 db-write-buffer-size: 33554432 - full-node: true + node-mode: full mainnet: false nat-addr: "" nat-wss-addr: "" @@ -201,9 +201,10 @@ bee-configs: p2p-wss-enable: true bee-local-ultralight-autotls: _inherit: "bee-local-dns" - full-node: false + node-mode: ultra-light p2p-wss-enable: true blockchain-rpc-endpoint: # ultralight nodes don't connect to the blockchain + swap-enable: false bootnode-local: _inherit: "bee-local" bootnode-mode: true @@ -214,7 +215,7 @@ bee-configs: bootnode-mode: true bee-local-light: _inherit: "bee-local" - full-node: false + node-mode: light bee-local-gc: _inherit: "bee-local" cache-capacity: 10 diff --git a/config/public-testnet.yaml b/config/public-testnet.yaml index 05ca6c4d..d1b3e41f 100644 --- a/config/public-testnet.yaml +++ b/config/public-testnet.yaml @@ -47,7 +47,7 @@ bee-configs: sepolia: _inherit: "" bootnode: ["/dnsaddr/testnet.ethswarm.org"] - full-node: true + node-mode: full checks: pt-pingpong: diff --git a/config/staging.yaml b/config/staging.yaml index e28ebeff..a8d94544 100644 --- a/config/staging.yaml +++ b/config/staging.yaml @@ -35,7 +35,7 @@ bee-configs: api-addr: ":1633" blockchain-rpc-endpoint: http://rpc-sepolia-haproxy.default.svc.swarm1.local bootnode: ["/dnsaddr/testnet.ethswarm.org"] - full-node: true + node-mode: full mainnet: false network-id: 10 p2p-addr: ":1634" diff --git a/config/testnet-bee-playground.yaml b/config/testnet-bee-playground.yaml index f0a3bc80..6361dc3e 100644 --- a/config/testnet-bee-playground.yaml +++ b/config/testnet-bee-playground.yaml @@ -52,7 +52,7 @@ bee-configs: bootnode: [] chequebook-enable: true data-dir: "/home/bee/.bee" - full-node: true + node-mode: full mainnet: false network-id: 12345 p2p-addr: :1634 diff --git a/pkg/orchestration/cluster.go b/pkg/orchestration/cluster.go index 6b52b571..a7081bda 100644 --- a/pkg/orchestration/cluster.go +++ b/pkg/orchestration/cluster.go @@ -23,6 +23,7 @@ type Cluster interface { FullNodeNames() (names []string) GlobalReplicationFactor(ctx context.Context, a swarm.Address) (grf int, err error) LightNodeNames() (names []string) + UltraLightNodeNames() (names []string) Name() string Namespace() string NodeGroup(name string) (ng NodeGroup, err error) diff --git a/pkg/orchestration/config_test.go b/pkg/orchestration/config_test.go index 6da47e2c..a951a035 100644 --- a/pkg/orchestration/config_test.go +++ b/pkg/orchestration/config_test.go @@ -60,3 +60,78 @@ func TestDeref(t *testing.T) { t.Errorf("Deref(new(false)) = %v, want false", got) } } + +func TestNodeModeHelpers(t *testing.T) { + t.Parallel() + + strPtr := func(s string) *string { return &s } + boolPtr := func(b bool) *bool { return &b } + + tests := []struct { + name string + cfg orchestration.Config + wantFull bool + wantLight bool + wantUltraLight bool + }{ + { + name: "explicit node-mode full", + cfg: orchestration.Config{NodeMode: strPtr("full")}, + wantFull: true, + wantLight: false, + wantUltraLight: false, + }, + { + name: "explicit node-mode light", + cfg: orchestration.Config{NodeMode: strPtr("light")}, + wantFull: false, + wantLight: true, + wantUltraLight: false, + }, + { + name: "explicit node-mode ultra-light", + cfg: orchestration.Config{NodeMode: strPtr("ultra-light")}, + wantFull: false, + wantLight: false, + wantUltraLight: true, + }, + { + name: "legacy full-node true", + cfg: orchestration.Config{FullNode: boolPtr(true)}, + wantFull: true, + wantLight: false, + wantUltraLight: false, + }, + { + name: "legacy full-node false with rpc (light)", + cfg: orchestration.Config{ + FullNode: boolPtr(false), + BlockchainRPCEndpoint: strPtr("http://localhost:8545"), + }, + wantFull: false, + wantLight: true, + wantUltraLight: false, + }, + { + name: "legacy full-node false without rpc (ultra-light)", + cfg: orchestration.Config{FullNode: boolPtr(false)}, + wantFull: false, + wantLight: false, + wantUltraLight: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.cfg.IsFullNode(); got != tt.wantFull { + t.Errorf("IsFullNode() = %v, want %v", got, tt.wantFull) + } + if got := tt.cfg.IsLightNode(); got != tt.wantLight { + t.Errorf("IsLightNode() = %v, want %v", got, tt.wantLight) + } + if got := tt.cfg.IsUltraLightNode(); got != tt.wantUltraLight { + t.Errorf("IsUltraLightNode() = %v, want %v", got, tt.wantUltraLight) + } + }) + } +} diff --git a/pkg/orchestration/k8s/cluster.go b/pkg/orchestration/k8s/cluster.go index cc689aba..bbb91de5 100644 --- a/pkg/orchestration/k8s/cluster.go +++ b/pkg/orchestration/k8s/cluster.go @@ -226,7 +226,17 @@ func (c *Cluster) NodeNames() (names []string) { // LightNodeNames returns a list of light node names func (c *Cluster) LightNodeNames() (names []string) { for name, node := range c.Nodes() { - if !orchestration.Deref(node.Config().FullNode) { + if node.Config().IsLightNode() { + names = append(names, name) + } + } + return names +} + +// UltraLightNodeNames returns a list of ultra-light node names +func (c *Cluster) UltraLightNodeNames() (names []string) { + for name, node := range c.Nodes() { + if node.Config().IsUltraLightNode() { names = append(names, name) } } @@ -237,7 +247,7 @@ func (c *Cluster) LightNodeNames() (names []string) { func (c *Cluster) FullNodeNames() (names []string) { for name, node := range c.Nodes() { cfg := node.Config() - if orchestration.Deref(cfg.FullNode) && !orchestration.Deref(cfg.BootnodeMode) { + if cfg.IsFullNode() && !orchestration.Deref(cfg.BootnodeMode) { names = append(names, name) } } @@ -249,7 +259,7 @@ func (c *Cluster) ShuffledFullNodeClients(ctx context.Context, r *rand.Rand) (or var res orchestration.ClientList for _, node := range c.Nodes() { cfg := node.Config() - if orchestration.Deref(cfg.FullNode) && !orchestration.Deref(cfg.BootnodeMode) { + if cfg.IsFullNode() && !orchestration.Deref(cfg.BootnodeMode) { res = append(res, node.Client()) } } @@ -464,7 +474,7 @@ func (c *Cluster) ClosestFullNodeClient(ctx context.Context, s *bee.Client) (*be } cfg := node.Config() // closet peer is not a full node. Check other peers in the same bin - if !orchestration.Deref(cfg.FullNode) || orchestration.Deref(cfg.BootnodeMode) { + if !cfg.IsFullNode() || orchestration.Deref(cfg.BootnodeMode) { skipList = append(skipList, addr) b-- continue diff --git a/pkg/orchestration/node.go b/pkg/orchestration/node.go index f450cd9c..de7ff6a1 100644 --- a/pkg/orchestration/node.go +++ b/pkg/orchestration/node.go @@ -113,7 +113,8 @@ type Config struct { DbDisableSeeksCompaction *bool `yaml:"db-disable-seeks-compaction,omitempty"` // disables db compactions triggered by seeks DbOpenFilesLimit *uint64 `yaml:"db-open-files-limit,omitempty"` // number of open files allowed by database DbWriteBufferSize *uint64 `yaml:"db-write-buffer-size,omitempty"` // size of the database write buffer in bytes - FullNode *bool `yaml:"full-node,omitempty"` // cause the node to start in full mode + FullNode *bool `yaml:"full-node,omitempty"` // cause the node to start in full mode (deprecated: use NodeMode) + NodeMode *string `yaml:"node-mode,omitempty"` // node operational mode: full, light, or ultra-light GasLimitFallback *uint64 `yaml:"gas-limit-fallback,omitempty"` // gas limit fallback when estimation fails for contract transactions Mainnet *bool `yaml:"mainnet,omitempty"` // triggers connect to main net bootnodes MinimumGasTipCap *uint64 `yaml:"minimum-gas-tip-cap,omitempty"` // minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap @@ -174,3 +175,28 @@ func Deref[T any](p *T) T { } return *p } + +// IsFullNode reports whether the node is configured as a full node. +// It checks NodeMode first; falls back to the deprecated FullNode bool. +func (c Config) IsFullNode() bool { + if c.NodeMode != nil && *c.NodeMode != "" { + return *c.NodeMode == "full" + } + return Deref(c.FullNode) +} + +// IsLightNode reports whether the node is configured as a light node. +func (c Config) IsLightNode() bool { + if c.NodeMode != nil && *c.NodeMode != "" { + return *c.NodeMode == "light" + } + return !Deref(c.FullNode) && Deref(c.BlockchainRPCEndpoint) != "" +} + +// IsUltraLightNode reports whether the node is configured as an ultra-light node. +func (c Config) IsUltraLightNode() bool { + if c.NodeMode != nil && *c.NodeMode != "" { + return *c.NodeMode == "ultra-light" + } + return !Deref(c.FullNode) && Deref(c.BlockchainRPCEndpoint) == "" +}