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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME ?= $(shell date -u '+%Y-%m-%d_%H:%M:%S')

# Linker flags for version information
# Optional minimum peer version for DHT gating can be provided via MIN_VER env/make var
LDFLAGS = -X github.com/LumeraProtocol/supernode/v2/supernode/cmd.Version=$(VERSION) \
-X github.com/LumeraProtocol/supernode/v2/supernode/cmd.GitCommit=$(GIT_COMMIT) \
-X github.com/LumeraProtocol/supernode/v2/supernode/cmd.BuildTime=$(BUILD_TIME) \
-X github.com/LumeraProtocol/supernode/v2/supernode/cmd.MinVer=$(MIN_VER) \
-X github.com/LumeraProtocol/supernode/v2/pkg/logtrace.DDAPIKey=$(DD_API_KEY) \
-X github.com/LumeraProtocol/supernode/v2/pkg/logtrace.DDSite=$(DD_SITE)

Expand Down
10 changes: 5 additions & 5 deletions p2p/kademlia/dht.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ func (s *DHT) newMessage(messageType int, receiver *Node, data interface{}) *Mes
IP: hostIP,
ID: s.ht.self.ID,
Port: s.ht.self.Port,
Version: requiredVersion(),
Version: localVersion(),
}
return &Message{
Sender: sender,
Expand Down Expand Up @@ -1399,21 +1399,21 @@ func (s *DHT) sendStoreData(ctx context.Context, n *Node, request *StoreDataRequ

// add a node into the appropriate k bucket, return the removed node if it's full
func (s *DHT) addNode(ctx context.Context, node *Node) *Node {
// Strict version gating: must match env and be non-empty.
// Minimum-version gating: reject nodes below configured minimum.
peerVer := ""
if node != nil {
peerVer = node.Version
}
if required, mismatch := versionMismatch(peerVer); mismatch {
if minRequired, tooOld := versionTooOld(peerVer); tooOld {
fields := logtrace.Fields{
logtrace.FieldModule: "p2p",
"required": required,
"min_required": minRequired,
"peer_version": strings.TrimSpace(peerVer),
}
if node != nil {
fields["peer"] = node.String()
}
logtrace.Debug(ctx, "Rejecting node due to version mismatch", fields)
logtrace.Debug(ctx, "Rejecting node: peer below minimum version", fields)
return nil
}
// Allow localhost for integration testing
Expand Down
8 changes: 4 additions & 4 deletions p2p/kademlia/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,15 @@ func (s *Network) handleConn(ctx context.Context, rawConn net.Conn) {
}
}

// Strict version gating: reject immediately on mismatch or missing
// Minimum-version gating: reject immediately if peer is below configured minimum
var senderVer string
if request != nil && request.Sender != nil {
senderVer = request.Sender.Version
}
if required, mismatch := versionMismatch(senderVer); mismatch {
logtrace.Debug(ctx, "Rejecting connection due to version mismatch", logtrace.Fields{
if minRequired, tooOld := versionTooOld(senderVer); tooOld {
logtrace.Debug(ctx, "Rejecting connection: peer below minimum version", logtrace.Fields{
logtrace.FieldModule: "p2p",
"required": required,
"min_required": minRequired,
"peer_version": strings.TrimSpace(senderVer),
})
return
Expand Down
4 changes: 2 additions & 2 deletions p2p/kademlia/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ type Node struct {
// port of the node
Port uint16 `json:"port,omitempty"`

// Version of the supernode binary (used for strict DHT gating)
Version string `json:"version,omitempty"`
// Version of the supernode binary (advertised to peers; may be used by min-version gating)
Version string `json:"version,omitempty"`

HashedID []byte
}
Expand Down
118 changes: 98 additions & 20 deletions p2p/kademlia/version_gate.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,112 @@
package kademlia

import (
"os"
"strconv"
"strings"
)

var requiredVer string
// localVer is the advertised version of this binary (e.g., v1.2.3),
// injected by the caller (supernode/cmd) at startup.
var localVer string

// SetRequiredVersion sets the version that peers must match to be accepted.
func SetRequiredVersion(v string) {
requiredVer = strings.TrimSpace(v)
// minVer is the optional minimum peer version to accept. If empty, gating is disabled.
var minVer string

// SetLocalVersion sets the version this node advertises to peers.
func SetLocalVersion(v string) {
localVer = strings.TrimSpace(v)
}

// requiredVersion returns the configured required version (build-time injected by caller).
func requiredVersion() string {
return requiredVer
// SetMinVersion sets the optional minimum required peer version for DHT interactions.
// When empty, version gating is disabled and all peers are accepted regardless of version string.
func SetMinVersion(v string) {
minVer = strings.TrimSpace(v)
}

// versionMismatch determines if the given peer version is unacceptable.
// Policy: required and peer must both be non-empty and exactly equal.
func versionMismatch(peerVersion string) (required string, mismatch bool) {
required = requiredVersion()
// Bypass strict gating during integration tests.
// Tests set os.Setenv("INTEGRATION_TEST", "true").
if os.Getenv("INTEGRATION_TEST") == "true" {
return required, false
// localVersion returns the configured advertised version.
func localVersion() string { return localVer }

// minimumVersion returns the configured minimum acceptable version; empty disables gating.
func minimumVersion() string { return minVer }

// versionTooOld reports whether the peerVersion is below the configured minimum version.
// If no minimum is configured, gating is disabled and this returns ("", false).
func versionTooOld(peerVersion string) (minRequired string, tooOld bool) {
minRequired = minimumVersion()
if strings.TrimSpace(minRequired) == "" {
// Gating disabled
return "", false
}

// Normalize inputs (strip leading 'v' and pre-release/build metadata)
p, okP := parseSemver(peerVersion)
m, okM := parseSemver(minRequired)
if !okM {
// Misconfigured minimum; disable gating to avoid accidental network splits.
return "", false
}
if !okP {
// Peer did not provide a valid version; treat as too old under a min-version policy.
return minRequired, true
}
// Compare peer >= min
if p[0] < m[0] {
return minRequired, true
}
if p[0] > m[0] {
return minRequired, false
}
if p[1] < m[1] {
return minRequired, true
}
if p[1] > m[1] {
return minRequired, false
}
if p[2] < m[2] {
return minRequired, true
}
return minRequired, false
}

// parseSemver parses versions like "v1.2.3", "1.2.3-alpha" into [major, minor, patch].
// Returns ok=false if no numeric major part is found.
func parseSemver(v string) ([3]int, bool) {
var out [3]int
s := strings.TrimSpace(v)
if s == "" {
return out, false
}
if s[0] == 'v' || s[0] == 'V' {
s = s[1:]
}
// Drop pre-release/build metadata
if i := strings.IndexAny(s, "-+"); i >= 0 {
s = s[:i]
}
parts := strings.Split(s, ".")
if len(parts) == 0 {
return out, false
}
peer := strings.TrimSpace(peerVersion)
if required == "" || peer == "" || peer != required {
return required, true
// Parse up to 3 numeric parts; missing parts default to 0
for i := 0; i < len(parts) && i < 3; i++ {
numStr := parts[i]
// Trim non-digit suffixes (e.g., "1rc1" -> "1")
j := 0
for j < len(numStr) && numStr[j] >= '0' && numStr[j] <= '9' {
j++
}
if j == 0 {
// No leading digits
if i == 0 {
return out, false
}
break
}
n, err := strconv.Atoi(numStr[:j])
if err != nil {
return out, false
}
out[i] = n
}
return required, false
return out, true
}
37 changes: 0 additions & 37 deletions sdk/helpers/github_helper.go

This file was deleted.

6 changes: 1 addition & 5 deletions sdk/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/LumeraProtocol/supernode/v2/sdk/adapters/lumera"
"github.com/LumeraProtocol/supernode/v2/sdk/config"
"github.com/LumeraProtocol/supernode/v2/sdk/event"
"github.com/LumeraProtocol/supernode/v2/sdk/helpers"
"github.com/LumeraProtocol/supernode/v2/sdk/log"
"github.com/LumeraProtocol/supernode/v2/sdk/net"
"google.golang.org/grpc/health/grpc_health_v1"
Expand Down Expand Up @@ -183,10 +182,7 @@ func (t *BaseTask) fetchSupernodesWithLoads(ctx context.Context, height int64) (
t.logger.Info(cctx, "reject supernode: status fetch failed", "error", err)
return nil
}
if reqVer := helpers.ResolveRequiredSupernodeVersion(); reqVer != "" && status.Version != reqVer {
t.logger.Info(cctx, "reject supernode: version mismatch", "expected", reqVer, "got", status.Version)
return nil
}
// Removed SDK-level version gating; rely on network/node policies instead.

// Compute load from running tasks (sum of task_count across services)
total := 0
Expand Down
Loading