109 lines
4.0 KiB
Python
109 lines
4.0 KiB
Python
"""Tests for CRDT flow dampening."""
|
|
|
|
import numpy as np
|
|
from src.crdt.flow_dampening import (
|
|
CRDTFlowSystem,
|
|
merge_flow_states,
|
|
peer_penalty,
|
|
simulate_drain_attack,
|
|
update_peer_flow,
|
|
)
|
|
|
|
|
|
class TestPeerFlowTracking:
|
|
def test_new_peer_no_penalty(self):
|
|
system = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
assert peer_penalty(system, "alice") == 1.0
|
|
|
|
def test_update_records_flow(self):
|
|
system = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
system = update_peer_flow(system, "alice", 50.0, 0.0)
|
|
assert "alice" in system.peers
|
|
assert system.peers["alice"].tracker.current_flow == 50.0
|
|
|
|
def test_multiple_peers_independent(self):
|
|
system = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
system = update_peer_flow(system, "alice", 50.0, 0.0)
|
|
system = update_peer_flow(system, "bob", 200.0, 0.0)
|
|
assert system.peers["alice"].tracker.current_flow == 50.0
|
|
assert system.peers["bob"].tracker.current_flow == 200.0
|
|
|
|
def test_high_flow_triggers_penalty(self):
|
|
system = CRDTFlowSystem(
|
|
default_total_value_ref=1000.0, default_threshold=0.1,
|
|
)
|
|
system = update_peer_flow(system, "alice", 200.0, 0.0)
|
|
penalty = peer_penalty(system, "alice")
|
|
assert penalty < 1.0
|
|
|
|
def test_pure_functional_no_mutation(self):
|
|
system = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
updated = update_peer_flow(system, "alice", 50.0, 0.0)
|
|
assert "alice" not in system.peers
|
|
assert "alice" in updated.peers
|
|
|
|
|
|
class TestFlowMerge:
|
|
def test_merge_commutativity(self):
|
|
a = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
a = update_peer_flow(a, "alice", 100.0, 0.0)
|
|
b = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
b = update_peer_flow(b, "alice", 50.0, 1.0)
|
|
|
|
ab = merge_flow_states(a, b)
|
|
ba = merge_flow_states(b, a)
|
|
assert ab.peers["alice"].tracker.current_flow == ba.peers["alice"].tracker.current_flow
|
|
assert ab.peers["alice"].tracker.last_update_time == ba.peers["alice"].tracker.last_update_time
|
|
|
|
def test_merge_idempotency(self):
|
|
a = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
a = update_peer_flow(a, "alice", 100.0, 0.0)
|
|
aa = merge_flow_states(a, a)
|
|
assert aa.peers["alice"].tracker.current_flow == a.peers["alice"].tracker.current_flow
|
|
|
|
def test_merge_takes_max_flow(self):
|
|
a = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
a = update_peer_flow(a, "alice", 100.0, 0.0)
|
|
b = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
b = update_peer_flow(b, "alice", 200.0, 0.0)
|
|
|
|
merged = merge_flow_states(a, b)
|
|
assert merged.peers["alice"].tracker.current_flow == 200.0
|
|
|
|
def test_merge_disjoint_peers(self):
|
|
a = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
a = update_peer_flow(a, "alice", 100.0, 0.0)
|
|
b = CRDTFlowSystem(default_total_value_ref=1000.0)
|
|
b = update_peer_flow(b, "bob", 50.0, 0.0)
|
|
|
|
merged = merge_flow_states(a, b)
|
|
assert "alice" in merged.peers
|
|
assert "bob" in merged.peers
|
|
|
|
|
|
class TestDrainSimulation:
|
|
def test_simulation_returns_arrays(self):
|
|
system = CRDTFlowSystem(
|
|
default_total_value_ref=10000.0, default_threshold=0.1,
|
|
)
|
|
result = simulate_drain_attack(
|
|
system, "attacker",
|
|
amounts=[100.0] * 10,
|
|
times=[float(i) for i in range(10)],
|
|
)
|
|
assert len(result["times"]) == 10
|
|
assert len(result["flows"]) == 10
|
|
assert len(result["penalties"]) == 10
|
|
|
|
def test_penalty_increases_with_drain(self):
|
|
system = CRDTFlowSystem(
|
|
default_total_value_ref=1000.0, default_threshold=0.1,
|
|
)
|
|
result = simulate_drain_attack(
|
|
system, "attacker",
|
|
amounts=[200.0] * 5,
|
|
times=[0.0, 0.1, 0.2, 0.3, 0.4],
|
|
)
|
|
# Penalties should decrease (get worse) as flow accumulates
|
|
assert result["penalties"][-1] <= result["penalties"][0]
|