-
Notifications
You must be signed in to change notification settings - Fork 0
[P2] feat(swarm): Implement Byzantine Swarm Protection #620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b2127a6
300799d
d0e0de2
291faba
867ae51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "thresholds": { | ||
| "safe": 0.3, | ||
| "watch": 0.5, | ||
| "suspicious": 0.7, | ||
| "critical": 0.9 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package marl | ||
|
|
||
| import ( | ||
| "sync" | ||
| "time" | ||
| ) | ||
|
|
||
| // StabilityController enforces action limits and cooling periods for MARL agents. | ||
| type StabilityController struct { | ||
| mu sync.RWMutex | ||
| actionDebt float64 | ||
| lastAction time.Time | ||
| cooldown time.Duration | ||
| maxContainment float64 | ||
| decayRate float64 // Debt reduction per second | ||
| lastDecay time.Time | ||
| } | ||
|
|
||
| // NewStabilityController initializes a controller with defined limits and decay rate. | ||
| func NewStabilityController(cooldown time.Duration, maxContainment float64, decayRate float64) *StabilityController { | ||
| now := time.Now() | ||
| return &StabilityController{ | ||
| cooldown: cooldown, | ||
| maxContainment: maxContainment, | ||
| decayRate: decayRate, | ||
| lastDecay: now, | ||
| } | ||
| } | ||
|
|
||
| // applyDecay reduces the action debt based on elapsed time. Must be called with lock held. | ||
| func (sc *StabilityController) applyDecay(now time.Time) { | ||
| elapsed := now.Sub(sc.lastDecay).Seconds() | ||
| if elapsed > 0 { | ||
| reduction := elapsed * sc.decayRate | ||
| sc.actionDebt -= reduction | ||
| if sc.actionDebt < 0 { | ||
| sc.actionDebt = 0 | ||
| } | ||
| sc.lastDecay = now | ||
| } | ||
| } | ||
|
|
||
| // TryRecordAction atomically checks if an action is allowed and records it if so. | ||
| // It also applies time-based decay to the action debt. | ||
| func (sc *StabilityController) TryRecordAction(cost float64, now time.Time) bool { | ||
| sc.mu.Lock() | ||
| defer sc.mu.Unlock() | ||
|
|
||
| sc.applyDecay(now) | ||
|
|
||
| // Check Cooldown | ||
| if now.Sub(sc.lastAction) < sc.cooldown { | ||
| return false | ||
| } | ||
|
|
||
| // Check Containment Rate (Action Debt) | ||
| if sc.actionDebt+cost > sc.maxContainment { | ||
| return false | ||
| } | ||
|
|
||
| sc.actionDebt += cost | ||
| sc.lastAction = now | ||
| return true | ||
| } | ||
|
|
||
| // GetActionDebt returns the current action debt (mainly for testing/monitoring). | ||
| func (sc *StabilityController) GetActionDebt(now time.Time) float64 { | ||
| sc.mu.Lock() | ||
| defer sc.mu.Unlock() | ||
| sc.applyDecay(now) | ||
| return sc.actionDebt | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package marl | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| func TestStability(t *testing.T) { | ||
| // 100ms cooldown, 1.0 max containment, decay 0.0 per sec (for testing strict limits) | ||
| sc := NewStabilityController(100*time.Millisecond, 1.0, 0.0) | ||
|
|
||
| start := time.Now() | ||
|
|
||
| // First action should pass | ||
| if !sc.TryRecordAction(0.5, start) { | ||
| t.Error("Expected action to be allowed") | ||
| } | ||
|
|
||
| // Immediate second action should fail due to cooldown | ||
| if sc.TryRecordAction(0.1, start.Add(10*time.Millisecond)) { | ||
| t.Error("Expected action to be throttled due to cooldown") | ||
| } | ||
|
|
||
| // Advance time past cooldown | ||
| afterCooldown := start.Add(150 * time.Millisecond) | ||
|
|
||
| // Should pass if under containment limit | ||
| if !sc.TryRecordAction(0.4, afterCooldown) { | ||
| t.Error("Expected action to be allowed after cooldown") | ||
| } | ||
|
|
||
| // Should fail if exceeding limit | ||
| if sc.TryRecordAction(0.2, afterCooldown.Add(150*time.Millisecond)) { | ||
| t.Error("Expected action to be throttled due to containment limit") | ||
| } | ||
| } | ||
|
|
||
| func TestStabilityDecay(t *testing.T) { | ||
| // Decay 1.0 per second | ||
| sc := NewStabilityController(100*time.Millisecond, 1.0, 1.0) | ||
|
|
||
| start := time.Now() | ||
|
|
||
| // Max out containment | ||
| sc.TryRecordAction(1.0, start) | ||
|
|
||
| // Should fail | ||
| if sc.TryRecordAction(0.1, start.Add(150*time.Millisecond)) { | ||
| t.Error("Expected action to be throttled due to containment limit") | ||
| } | ||
|
|
||
| // Advance time by 0.5s, debt should decay by 0.5 | ||
| afterDecay := start.Add(500 * time.Millisecond) | ||
|
|
||
| // Should pass now that debt has decayed | ||
| if !sc.TryRecordAction(0.4, afterDecay) { | ||
| t.Error("Expected action to be allowed after debt decay") | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package governance | ||
|
|
||
| import ( | ||
| "sync" | ||
| ) | ||
|
|
||
| // Policy defines the rules for swarm node participation. | ||
| type Policy struct { | ||
| MinReputation float64 | ||
| QuorumSize int | ||
| } | ||
|
|
||
| // SwarmGovernor enforces governance policies on the node network. | ||
| type SwarmGovernor struct { | ||
| mu sync.RWMutex | ||
| Policy Policy | ||
| } | ||
|
|
||
| // NewSwarmGovernor initializes the governor with a policy. | ||
| func NewSwarmGovernor(policy Policy) *SwarmGovernor { | ||
| return &SwarmGovernor{ | ||
| Policy: policy, | ||
| } | ||
| } | ||
|
|
||
| // ValidateProposal checks if a node is authorized to participate in consensus. | ||
| func (sg *SwarmGovernor) ValidateProposal(nodeReputation float64) bool { | ||
| sg.mu.RLock() | ||
| defer sg.mu.RUnlock() | ||
| return nodeReputation >= sg.Policy.MinReputation | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package governance | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestGovernance(t *testing.T) { | ||
| policy := Policy{MinReputation: 0.7, QuorumSize: 3} | ||
| gov := NewSwarmGovernor(policy) | ||
|
|
||
| if gov.ValidateProposal(0.8) == false { | ||
| t.Error("Proposal should be accepted for compliant reputation") | ||
| } | ||
|
|
||
| if gov.ValidateProposal(0.5) == true { | ||
| t.Error("Proposal should be rejected for low reputation") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| module phoenix/telemetry/serialization | ||
|
|
||
| go 1.25.0 | ||
|
|
||
| require github.com/segmentio/encoding v0.5.4 | ||
|
|
||
| require ( | ||
| github.com/segmentio/asm v1.1.3 // indirect | ||
| golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= | ||
| github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= | ||
| github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= | ||
| github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= | ||
| golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ= | ||
| golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package serialization | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "github.com/segmentio/encoding/json" // Utilizing high-performance JSON encoder | ||
| ) | ||
|
|
||
| // OptimizedMarshaler replaces standard library JSON encoding with high-perf alternatives. | ||
| func OptimizedMarshaler(v interface{}) ([]byte, error) { | ||
| return json.Marshal(v) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package serialization | ||
|
|
||
| import ( | ||
| "testing" | ||
| ) | ||
|
|
||
| type TestStruct struct { | ||
| ID int `json:"id"` | ||
| Value string `json:"value"` | ||
| } | ||
|
|
||
| func TestOptimizer(t *testing.T) { | ||
| data := TestStruct{ID: 1, Value: "test"} | ||
| encoded, err := OptimizedMarshaler(data) | ||
| if err != nil { | ||
| t.Fatalf("Failed to marshal: %v", err) | ||
| } | ||
| if string(encoded) == "" { | ||
|
Comment on lines
+12
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Strengthen assertions by validating the actual JSON output and error paths Asserting only that the output is non-empty is too weak. Please:
This will better verify correctness and error handling of the marshaler. Suggested implementation: import (
"encoding/json"
"testing"
)func TestOptimizer(t *testing.T) {
data := TestStruct{ID: 1, Value: "test"}
encoded, err := OptimizedMarshaler(data)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
if len(encoded) == 0 {
t.Fatal("Marshaled data is empty")
}
var decoded map[string]interface{}
if err := json.Unmarshal(encoded, &decoded); err != nil {
t.Fatalf("Failed to unmarshal encoded JSON: %v", err)
}
id, ok := decoded["id"].(float64)
if !ok || id != 1 {
t.Fatalf("Unexpected id field: %#v", decoded["id"])
}
value, ok := decoded["value"].(string)
if !ok || value != "test" {
t.Fatalf("Unexpected value field: %#v", decoded["value"])
}
}
func TestOptimizerUnsupportedType(t *testing.T) {
type Unsupported struct {
Ch chan int `json:"ch"`
}
data := Unsupported{Ch: make(chan int)}
encoded, err := OptimizedMarshaler(data)
if err == nil {
t.Fatalf("Expected error for unsupported type, got nil (encoded=%q)", string(encoded))
}
} |
||
| t.Error("Marshaled data is empty") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type SystemState uint8 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,30 +16,51 @@ const ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StateCompromised SystemState = 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Config struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Thresholds struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Safe float64 `json:"safe"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Watch float64 `json:"watch"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Suspicious float64 `json:"suspicious"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Critical float64 `json:"critical"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } `json:"thresholds"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Warden struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CurrentState SystemState | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Throttling float64 // 0.0 (None) to 1.0 (Full Block) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Config Config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func NewWarden() *Warden { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func NewWarden(configPath string) (*Warden, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configFile, err := os.ReadFile(configPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to read config file: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var config Config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(configFile, &config); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to parse config file: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &Warden{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CurrentState: StateSafe, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Throttling: 0.0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Config: config, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Validate the loaded thresholds to ensure they are sane and ordered. Given that
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // EvaluateSDI maps the System Disorder Index to a discrete state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (w *Warden) EvaluateSDI(sdi float64) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| oldState := w.CurrentState | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < 0.3: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < w.Config.Thresholds.Safe: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.CurrentState = StateSafe | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < 0.5: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < w.Config.Thresholds.Watch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.CurrentState = StateWatch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < 0.7: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < w.Config.Thresholds.Suspicious: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.CurrentState = StateSuspicious | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < 0.9: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case sdi < w.Config.Thresholds.Critical: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.CurrentState = StateCritical | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.CurrentState = StateCompromised | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -71,7 +94,11 @@ func (w *Warden) ApplyAction() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Println("Phoenix Warden starting with Finite-State Controller...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warden := NewWarden() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warden, err := NewWarden("../../config/warden.json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Consider making the config path configurable rather than hardcoded relative paths. Hardcoding "../../config/warden.json" couples startup to a specific working directory and layout, which is likely to break under different launch setups (systemd, containers, tests). Prefer a configurable source (e.g., flag, env var, or embedded default with override) so startup doesn’t depend on cwd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The hard-coded Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fmt.Printf("Error initializing Warden: %v\n", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Simulate rising threat | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warden.EvaluateSDI(0.15) // Safe | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,32 @@ | ||
| package main | ||
|
|
||
| import "testing" | ||
| import ( | ||
| "os" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestWardenFSM(t *testing.T) { | ||
| w := NewWarden() | ||
|
|
||
| // Create a temporary config file for the test | ||
| configContent := `{ | ||
| "thresholds": { | ||
| "safe": 0.3, | ||
| "watch": 0.5, | ||
| "suspicious": 0.7, | ||
| "critical": 0.9 | ||
| } | ||
| }` | ||
| configFile := "test_warden.json" | ||
| err := os.WriteFile(configFile, []byte(configContent), 0644) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create temp config file: %v", err) | ||
| } | ||
| defer os.Remove(configFile) | ||
|
Comment on lines
+18
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Use t.TempDir and unique paths instead of a fixed filename in the working directory A fixed filename in the working directory can cause tests to race with each other, break under concurrent runs, or fail on read-only directories. Use t.TempDir() and build the path with filepath.Join so each test gets its own temp file and automatic cleanup, instead of relying on os.Remove. Suggested implementation: // Create a config file in a temporary directory for the test
configContent := `{
"thresholds": {
"safe": 0.3,
"watch": 0.5,
"suspicious": 0.7,
"critical": 0.9
}
}`
dir := t.TempDir()
configFile := filepath.Join(dir, "warden_config.json")
err := os.WriteFile(configFile, []byte(configContent), 0o644)
if err != nil {
t.Fatalf("Failed to create temp config file: %v", err)
}
w, err := NewWarden(configFile)
|
||
|
|
||
| w, err := NewWarden(configFile) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create Warden with config: %v", err) | ||
| } | ||
|
|
||
| // Initial State | ||
| if w.CurrentState != StateSafe { | ||
| t.Errorf("Expected initial state Safe, got %v", w.CurrentState) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValidateProposalonly checksMinReputationand never usesPolicy.QuorumSize, so proposals can be accepted even when the consensus set is below the configured quorum. In this commit,QuorumSizeis defined and set in tests but never read, which means the advertised Byzantine/quorum protection is not actually enforced.Useful? React with 👍 / 👎.