Arbitration — Algorithm Extraction
Language-agnostic pseudocode extracted from Phoenix Python source.
Source files: arbitration.py (716L), vrf.py (664L).
1. Three Dispute Levels
// ARBITERS_REQUIRED per dispute level
ARBITERS_REQUIRED = {
0: 3, // L0 — minor disputes (3 arbiters)
1: 5, // L1 — significant disputes (5 arbiters)
2: 7, // L2 — major / systemic disputes (7 arbiters)
}
enum DisputeLevel:
L0 // Low stakes; contract disagreements < threshold
L1 // Mid stakes; significant reputation impact
L2 // High stakes; systemic / constitutional implications
function determine_dispute_level(dispute: Dispute) -> DisputeLevel:
stake_value = dispute.contract.stake_amount
if stake_value >= HIGH_STAKE_THRESHOLD: return L2
if stake_value >= MID_STAKE_THRESHOLD: return L1
return L0
2. VRF Arbiter Selection: Deterministic, Unpredictable, Verifiable
Selection is based on ECVRF (RFC 9381). See also theta-consensus-extraction.md §6.
// Eligibility requirements for arbiter pool
function is_eligible_arbiter(node: Node) -> bool:
return (
node.reputation[ARBITRATION] >= 5000 AND
node.reputation[EXECUTION] >= 3000 AND
node.phase in [ESTABLISHED, SENIOR, ELDER] AND
not node.is_party_to_dispute AND // cannot arbitrate own dispute
node.ban_until_epoch <= current_epoch
)
function select_arbiters(
dispute_id: bytes32,
epoch: int64,
eligible: Node[],
level: DisputeLevel,
) -> Node[]:
count = ARBITERS_REQUIRED[level]
seed = sha256(dispute_id || epoch)
scored = []
for node in eligible:
output, proof = vrf_prove(node.secret_key, seed)
scored.append((output, proof, node))
// Verify each VRF output (done by independent verifier)
verified = [(out, node) for out, proof, node in scored
if vrf_verify(node.public_key, seed, out, proof)]
filtered = anti_collusion_filter(verified, count)
sorted_by_vrf = sort(filtered, key=lambda x: x[0])
return [node for _, node in sorted_by_vrf[:count]]
// Anti-oligarchy cap: max 30% of selected arbiters from same controlling entity
ANTI_OLIGARCHY_CAP = 0.30
function anti_collusion_filter(scored: (bytes32, Node)[], required: int) -> (bytes32, Node)[]:
result = []
entity_counts = {}
cap = max(1, int(required * ANTI_OLIGARCHY_CAP))
for output, node in sorted(scored, key=lambda x: x[0]):
entity = node.controlling_entity // null if independent
entity_counts[entity] = entity_counts.get(entity, 0) + 1
if entity_counts[entity] <= cap:
result.append((output, node))
return result
3. Commit-Reveal Voting Protocol
Two-phase voting prevents early votes from influencing later ones.
enum VoteValue: FOR_CLAIMANT, FOR_RESPONDENT, NO_DECISION
struct CommitRevealVote:
arbiter: NodeId
dispute_id: bytes32
// Phase 1 — COMMIT
commitment: bytes32 // sha256(vote_value || nonce)
committed: bool
// Phase 2 — REVEAL
vote: VoteValue?
nonce: bytes32?
revealed: bool
// Phase 1: Commit
function commit_vote(arbiter: NodeId, dispute_id: bytes32, vote: VoteValue, nonce: bytes32) -> bytes32:
commitment = sha256(encode(vote) || nonce)
store CommitRevealVote {
arbiter: arbiter,
dispute_id: dispute_id,
commitment: commitment,
committed: true,
revealed: false,
}
return commitment
// Phase 2: Reveal (after all arbiters commit OR commit window closes)
function reveal_vote(arbiter: NodeId, dispute_id: bytes32, vote: VoteValue, nonce: bytes32):
record = get_commit_record(arbiter, dispute_id)
require record.committed
require not record.revealed
require sha256(encode(vote) || nonce) == record.commitment
record.vote = vote
record.nonce = nonce
record.revealed = true
function tally_votes(dispute_id: bytes32) -> VoteValue:
votes = get_revealed_votes(dispute_id)
for_claimant = count(v for v in votes if v.vote == FOR_CLAIMANT)
for_respondent = count(v for v in votes if v.vote == FOR_RESPONDENT)
if for_claimant > for_respondent: return FOR_CLAIMANT
if for_respondent > for_claimant: return FOR_RESPONDENT
return NO_DECISION // tie → escalate or dismiss
4. DisputePhase State Machine
FILING --evidence submitted--> SELECTION --arbiters selected--> DELIBERATION
--commit phase--> COMMIT --all committed or timeout--> REVEAL
--all revealed or timeout--> RESOLUTION
--if appealed--> APPEAL
--if appeal rejected or window closed--> CLOSED
enum DisputePhase:
FILING // Dispute submitted; evidence gathering
SELECTION // VRF arbiter selection running
DELIBERATION // Arbiters reviewing evidence; commit window open
COMMIT // All arbiters must submit commitments
REVEAL // Commitments received; reveal window open
RESOLUTION // Votes tallied; ruling computed
APPEAL // Appeal filed; escalated to L+1 or constitutional review
CLOSED // Final; no further action possible
PHASE_TIMEOUTS_EPOCHS = {
FILING: 10,
SELECTION: 3,
DELIBERATION: 20,
COMMIT: 10,
REVEAL: 10,
RESOLUTION: 5,
APPEAL: 30,
}
function advance_phase(dispute: Dispute, event: DisputeEvent):
match (dispute.phase, event):
(FILING, EvidenceSubmitted): dispute.phase = SELECTION
(SELECTION, ArbitersSelected): dispute.phase = DELIBERATION
(DELIBERATION, CommitWindowOpen): dispute.phase = COMMIT
(COMMIT, AllCommitted): dispute.phase = REVEAL
(COMMIT, Timeout): dispute.phase = REVEAL // partial commits allowed
(REVEAL, AllRevealed): dispute.phase = RESOLUTION
(REVEAL, Timeout): dispute.phase = RESOLUTION
(RESOLUTION, AppealFiled): dispute.phase = APPEAL
(RESOLUTION, NoAppeal): dispute.phase = CLOSED
(APPEAL, Resolved): dispute.phase = CLOSED
5. Appeal Mechanism with Escalating Stakes
APPEAL_STAKE_MULTIPLIER = 2 // each appeal level doubles required stake
APPEAL_MAX_LEVEL = 2 // L2 is max; no appeal from L2
function can_appeal(dispute: Dispute, appellant: NodeId) -> (bool, string):
if dispute.level == L2:
return (false, "L2 disputes are not appealable")
if dispute.phase != RESOLUTION:
return (false, "can only appeal in RESOLUTION phase")
appeal_stake = dispute.contract.stake_amount * APPEAL_STAKE_MULTIPLIER ** (dispute.level + 1)
if appellant.available_stake < appeal_stake:
return (false, "insufficient stake for appeal")
return (true, "")
function file_appeal(dispute: Dispute, appellant: NodeId):
required, reason = can_appeal(dispute, appellant)
require required, reason
appeal_stake = dispute.contract.stake_amount * APPEAL_STAKE_MULTIPLIER ** (dispute.level + 1)
lock_stake(appellant, appeal_stake)
// Escalate to next dispute level
new_level = dispute.level + 1 // L0 → L1 → L2
new_arbiter_count = ARBITERS_REQUIRED[new_level]
appeal = Appeal {
original_dispute_id: dispute.id,
appellant: appellant,
staked: appeal_stake,
new_level: new_level,
new_arbiters: select_arbiters(dispute.id, current_epoch, eligible_arbiters(), new_level),
phase: DELIBERATION,
}
dispute.phase = APPEAL
dispute.active_appeal = appeal
// If appeal fails: appellant loses staked amount (distributed to original arbiters)
// If appeal succeeds: original arbiters lose their arbitration rep for the ruling
function resolve_appeal(appeal: Appeal, result: VoteValue):
original = get_dispute(appeal.original_dispute_id)
if result != original.ruling:
// Appeal succeeded: original ruling overturned
penalize_arbiters(original.arbiters, severity=MODERATE)
refund_stake(appeal.appellant, appeal.staked)
apply_new_ruling(original, result)
else:
// Appeal failed: appellant loses stake
distribute_stake(appeal.staked, original.arbiters)
6. Resolution: Majority Ruling with Evidence Weight
struct DisputeResolution:
dispute_id: bytes32
ruling: VoteValue
vote_tally: Map<VoteValue, int>
evidence_weight: float // weighted confidence 0.0–1.0
rep_consequences: Map<NodeId, (Domain, int64)> // reputation effects
stake_distribution: Map<NodeId, int64>
function resolve_dispute(dispute: Dispute) -> DisputeResolution:
tally = tally_votes(dispute.id)
ruling = majority_ruling(tally)
// Evidence weight: how strongly the evidence supported the ruling
// Higher weight → larger reputation delta for correct predictors
evidence_weight = compute_evidence_weight(dispute.evidence, ruling)
// Reputation consequences
rep_effects = {}
for arbiter in dispute.arbiters:
if arbiter.vote == ruling:
// Voted with majority: earn arbitration rep
delta = (2000 * evidence_weight) // up to 2000 bps for ResolveDispute
rep_effects[arbiter.id] = (ARBITRATION, delta)
else:
// Voted against majority: lose arbitration rep
rep_effects[arbiter.id] = (ARBITRATION, -1000)
// Stake distribution
winner = dispute.claimant if ruling == FOR_CLAIMANT else dispute.respondent
stake_dist = distribute_stake_to_winner(dispute.stakes, winner)
return DisputeResolution {
ruling: ruling,
evidence_weight: evidence_weight,
rep_consequences: rep_effects,
stake_distribution: stake_dist,
}
7. Dependencies
| Module | Interaction |
|---|---|
| θ Consensus | VRF-based arbiter selection; finality of rulings |
| λ Reputation | Arbiter eligibility thresholds; rep effects of rulings |
| κ Rule Engine | Policy gates on dispute filing and appeal |
| ι State Fork | Evidence can reference fork-local events |
| π Governance | L2 disputes may escalate to constitutional review |
Links
| [[concepts/θ-consensus | θ Consensus]] · [[concepts/λ-reputation | λ Reputation]] · [[spec/S09-arbitration | S09 Arbitration Spec]] · [[reference/extractions/theta-consensus-extraction | θ Extraction]] · [[reference/extractions/lambda-reputation-extraction | λ Extraction]] |