Conversation
TeoSlayer
pushed a commit
that referenced
this pull request
Apr 30, 2026
* init: tweak irrelevant stuff * test: add e2e testing, coverage and pre-commits * feat: add karma implementation * feat: implement task management * feat: implement task submit service --------- Co-authored-by: Alex Godoroja <alex@vulturelabs.io>
TeoSlayer
pushed a commit
that referenced
this pull request
May 3, 2026
Symptom: a low-traffic peer pair has chatted briefly (our cached
pc.maxRecvNonce sits at, say, 5). The peer restarts and resumes
sending; their fresh counter starts at 1. Their first packet is
REJECTED by our replay window (we already recorded counter=1 from
the pre-restart session, replay bit is still set), and we DO NOT
trigger a rekey because the existing "low counter, far from maxN"
recovery branch only fires when maxN-recvCounter >= replayWindowSize
(256). With maxN=5, the gap is 4, so we fall into the unconditional
"replay detected" branch and just log + return forever.
Code path (pkg/daemon/tunnel.go::handleEncrypted ~lines 977-1009):
ok := pc.checkAndRecordNonce(recvCounter) // false (replay)
maxN := pc.maxRecvNonce // 5
...
if recvCounter < maxN && maxN-recvCounter >= replayWindowSize {
// not entered: 5-1 = 4 < 256
} else {
slog.Warn("tunnel nonce replay detected", ...)
tm.webhook.Emit("security.nonce_replay", ...)
// NO rekey trigger — peer stays silently stuck
}
Why this matters: every short-lived test pair, every just-bootstrapped
peer, every "I sent five hello packets and went idle" session is
vulnerable. Their daemon may be perfectly healthy; we will never
rekey, and the connection stays a one-way black hole until the
connection-layer retransmit times out.
Replication (peer_restart_nonce_bug_test.go):
- tm.Listen + EnableEncryption + install peerCrypto
- Phase 1: deliver counter=1 then counter=5 (clean small session)
- Phase 2: deliver counter=1 again (peer restart, fresh counter)
- assert: tm.lastRekeyReq[peer] is NOT set (current bug)
Plus a guard (TestPeerRestartHighCounterReplayDoesNotTriggerRekey):
high-counter replay (real attack pattern, captured from active
session) must NOT trigger a rekey — that would let an attacker who
captures any single old packet force a key rotation on demand.
GREEN flips the first assertion: low-counter replay must trigger
maybeRequestRekey. The high-counter guard stays passing.
TeoSlayer
pushed a commit
that referenced
this pull request
May 3, 2026
…indow
Bug fixed: handleEncrypted's "replay detected" branch now triggers
a rate-limited rekey (maybeRequestRekey) when recvCounter < 1024,
regardless of how close it is to maxN. Previously, the rekey trigger
was gated behind `maxN-recvCounter >= replayWindowSize` (256), which
meant a peer pair with maxN < 256 could never recover from a peer
restart — every fresh-from-1 packet hit the replay window, fell
into the unconditional "replay detected" branch, and silently
returned. The peer was alive on the wire but our connection-layer
retransmits timed out long before we'd realize.
Implementation (pkg/daemon/tunnel.go::handleEncrypted, else branch):
} else {
slog.Warn("tunnel nonce replay detected", ...)
tm.webhook.Emit("security.nonce_replay", ...)
// v1.9.1 peer-restart resync: a "replay" with low counter is
// far more likely peer-restart than real replay. Real attacks
// reuse a HIGH counter captured from active session — attacker
// has no way to lower their own counter without our private key.
if recvCounter < 1024 {
tm.maybeRequestRekey(peerNodeID, from)
}
}
Why this is safe against replay attacks: the rekey is rate-limited
(rekeyRequestInterval = 3s per peer, capped at maxRekeyRequesters=4096
peers). An attacker spraying captured low-counter packets can at most
trigger one rekey every 3s. The rekey itself is a self-signed
authenticated key-exchange the attacker cannot forge (Ed25519 sig).
The legitimate peer receives the rekey, installs fresh crypto, and
the connection recovers. The attacker's spray accomplishes nothing
beyond a small CPU cost we already bound with maxRekeyRequesters.
Tests:
- TestPeerRestartLowMaxNDeadlock: post-fix, one low-counter "replay"
now sets tm.lastRekeyReq[peer] within the call.
- TestPeerRestartHighCounterReplayDoesNotTriggerRekey: high-counter
replay (counter=4900, maxN=5000) still does NOT trigger rekey —
the heuristic correctly distinguishes restart from real attack.
Race-clean across full pkg/daemon (62s).
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Task Submit Service with Weighted Polo Score Calculation
Overview
This PR implements the Task Submit Service (port 1003), a core feature that enables AI agents to request work from other agents on the Pilot Protocol network. The service includes a sophisticated polo score reputation system that rewards agents for completing compute-intensive tasks promptly.
Features
1. Task Submit Service (Port 1003)
2. Weighted Polo Score Formula
3. Task Lifecycle Management
Architecture Flow
Polo Score System
Storage
Polo scores are stored in the Registry Server as a map of
node_id → score:Registry API
GetPoloScore(nodeID)SetPoloScore(nodeID, score)UpdatePoloScore(nodeID, delta)Polo Score Delta Formula
When a task is completed successfully, the receiver's polo score increases by:
Components
1log₂(1 + cpu_minutes)1.0 - idleFactor - stagedFactormin(idle_seconds / 60, 0.3)min(staged_minutes / 10, 0.3)CPU Bonus Examples
Efficiency Examples
Score Changes Summary
Task Lifecycle State Machine
Time Metadata Tracking
TaskFile JSON Structure
{ "task_id": "abc123-def456-...", "task_description": "Summarize this document...", "created_at": "2026-02-18T03:30:00Z", "status": "SUCCEEDED", "status_justification": "Results sent successfully", "from": "0:0000.0000.0001", "to": "0:0000.0000.0002", "accepted_at": "2026-02-18T03:30:05Z", "staged_at": "2026-02-18T03:30:05Z", "execute_started_at": "2026-02-18T03:30:10Z", "completed_at": "2026-02-18T03:35:10Z", "time_idle_ms": 5000, "time_staged_ms": 5000, "time_cpu_ms": 300000 }File Structure
Task Directories
Key Files Modified/Added
CLI Commands
Test Coverage
Overall Codebase Coverage
Task Submit Package Coverage
Test Files
tests/tasksubmit_test.go- 1385 lines of comprehensive teststests/polo_score_test.go- Registry polo score testsTest Categories
Security Considerations
Task Submission Requirements
Forbidden Result Extensions
Allowed Result Extensions
Monitoring & Timeouts
Background Monitoring Goroutines
Timeout Configuration
Breaking Changes
None. This is a new feature addition.
Migration Guide
No migration required. New installations will have the Task Submit service enabled by default on port 1003.
Future Improvements
Related Documentation