Skip to content

treehauslabs/Tally

Repository files navigation

Tally

A Bitswap-inspired peer reputation and rate limiting module for Swift. Tracks bytes exchanged, latency, success rate, and proof-of-work challenges to compute a composite reputation score per peer. Under load, only high-reputation peers get served.

let tally = Tally()
let peer = PeerID(publicKey: "a1f2e3d...")

tally.recordReceived(peer: peer, bytes: 4096)
tally.recordSent(peer: peer, bytes: 2048)
tally.recordLatency(peer: peer, microseconds: 5000)
tally.recordSuccess(peer: peer)

tally.reputation(for: peer)    // 0.72
tally.shouldAllow(peer: peer)  // true — good reputation, under rate limit

How It Works

Reputation Score

Each peer's reputation is a weighted composite of four factors (0.0 to 1.0):

Factor Weight Measures
Reciprocity 0.4 1 / (1 + debtRatio) — peers who give back score higher
Latency 0.2 baseline / (meanLatency + 1) — fast peers score higher
Success rate 0.2 successes / (successes + failures) — reliable peers score higher
Challenges 0.2 Proof-of-work completions — bootstrapped peers can earn credit

Rate-Aware Gating

Instead of a fixed allow/deny threshold, Tally adapts based on current load:

ratePressure = currentRate / rateLimitBytesPerSecond

pressure < 0.5  →  allow everyone (plenty of capacity)
pressure 0.5–1.0 →  increasingly selective (reputation must exceed threshold)
pressure >= 1.0  →  only reputation >= 0.8 gets through

When you're well under your rate limit, even unknown peers get served. As load climbs, Tally progressively gates to high-reputation peers only.

Proof of Work Challenges

New peers with no exchange history can bootstrap reputation by solving SHA256 proof-of-work challenges:

let challenge = tally.issueChallenge()
// peer solves: find `solution` where SHA256(nonce || solution) has N leading zero bits
let verified = tally.verifyChallenge(challenge, solution: peerSolution, peer: peer)
// verified == true → peer.challengeHardness += difficulty → reputation improves

This provides Sybil resistance — creating many identities is cheap, but earning reputation for each requires real computation.

Requirements

  • Swift 6.0+
  • macOS 13+ / iOS 16+

Installation

.package(url: "https://github.com/treehauslabs/Tally.git", from: "1.0.0"),

Then add to your target:

.target(name: "YourTarget", dependencies: ["Tally"])

Usage

Recording exchanges

import Tally

let tally = Tally()
let peer = PeerID(publicKey: "a1f2e3d4...")

tally.recordSent(peer: peer, bytes: responseData.count)
tally.recordReceived(peer: peer, bytes: incomingData.count)
tally.recordLatency(peer: peer, microseconds: elapsed)
tally.recordSuccess(peer: peer)
tally.recordFailure(peer: peer)
tally.recordRequest(peer: peer)

Gating outbound data

if tally.shouldAllow(peer: peer) {
    let data = fetchData(for: cid)
    tally.recordSent(peer: peer, bytes: data.count)
    send(data, to: peer)
} else {
    sendDenial(to: peer)
}

Proof of work bootstrapping

// Server side: issue challenge
let challenge = tally.issueChallenge()
send(challenge, to: peer)

// Client side: solve
let solver = ChallengeSolver()
let solution = solver.solve(challenge)
send(solution, to: server)

// Server side: verify and credit
tally.verifyChallenge(challenge, solution: solution, peer: peer)

Configuration

let tally = Tally(config: TallyConfig(
    weights: ReputationWeights(
        reciprocity: 0.4,
        latency: 0.2,
        successRate: 0.2,
        challenges: 0.2
    ),
    latencyBaseline: 100_000,          // microseconds
    challengeDifficulty: 16,           // leading zero bits
    rateLimitBytesPerSecond: 10_000_000,
    rateWindow: 1.0,                   // seconds
    maxPeers: 10_000
))

Observability

tally.reputation(for: peer)
tally.debtRatio(for: peer)
tally.peerLedger(for: peer)
tally.ratePressure()

let m = tally.metrics
// m.allowed, m.denied, m.totalBytesSent, m.totalBytesReceived
// m.challengesIssued, m.challengesVerified

API

Method Description
recordSent(peer:bytes:) Record bytes sent to a peer (increases debt).
recordReceived(peer:bytes:) Record bytes received from a peer (credits them).
recordLatency(peer:microseconds:) Record response latency for a peer.
recordSuccess(peer:) Record a successful interaction.
recordFailure(peer:) Record a failed interaction.
recordRequest(peer:) Increment request count.
shouldAllow(peer:) -> Bool Rate-aware + reputation-based allow/deny.
reputation(for:) -> Double Composite reputation score (0.0–1.0).
debtRatio(for:) -> Double Raw debt ratio for a peer.
ratePressure() -> Double Current rate pressure (0.0 = idle, 1.0 = at limit).
issueChallenge() -> Challenge Create a proof-of-work challenge.
verifyChallenge(_:solution:peer:) -> Bool Verify and credit a solved challenge.
peerLedger(for:) -> PeerLedger? Full ledger for a peer.
allPeers() -> [PeerID] All tracked peer IDs.
peerCount -> Int Number of tracked peers.
resetPeer(_:) Remove a peer's ledger.
metrics -> TallyMetrics Aggregate stats.

Design

  • Lock-based, no actor — all state behind OSAllocatedUnfairLock for nanosecond-scale operations.
  • Bitswap debt ratior = bytes_sent / (bytes_received + 1), same formula as IPFS.
  • Composite reputation — weighted blend of reciprocity, latency, success rate, and proof-of-work.
  • Rate-aware gating — permissive when idle, selective under load.
  • SHA256 proof of work — Sybil-resistant reputation bootstrapping for new peers.
  • Zero external dependencies — uses only Foundation and CryptoKit.

Performance

Benchmarked on Apple Silicon (M-series), release mode:

Operation Time Notes
shouldAllow (fresh peer) 48ns Lock + dictionary miss + rate check
shouldAllow (known peer) 69ns Lock + lookup + reputation + rate check
reputation lookup 33ns Lock + lookup + weighted score
recordSent 89ns Lock + lookup + update + rate window
recordLatency 54ns Lock + lookup + running stats
mixed (80% check / 20% record) 81ns Realistic workload

Testing

swift test

33 tests across 3 suites: PeerLedger (debt ratio, reciprocity, success rate, latency scoring, reputation composition, custom weights), Tally (recording, gating, metrics, rate pressure, peer management), Challenge (solving, verification, accumulation, reputation bootstrapping).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages