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
[[concepts/θ-consensus θ Consensus]] · [[concepts/λ-reputation λ Reputation]] · [[spec/S09-arbitration S09 Arbitration Spec]] · [[reference/extractions/theta-consensus-extraction θ Extraction]] · [[reference/extractions/lambda-reputation-extraction λ Extraction]]

Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.