Skip to content

feat(contracts): add permissionless arbitrator pool selection - #289

Merged
jotel-dev merged 2 commits into
Nullifier-Systems:mainfrom
Tijesunimi004:feat/arbitrator-pool-selection
Jul 29, 2026
Merged

feat(contracts): add permissionless arbitrator pool selection#289
jotel-dev merged 2 commits into
Nullifier-Systems:mainfrom
Tijesunimi004:feat/arbitrator-pool-selection

Conversation

@Tijesunimi004

Copy link
Copy Markdown
Contributor

Closes #279.

What changed

Adds an opt-in arbitrator pool to contracts/escrow/src/lib.rs:

  • join_arbitrator_pool / leave_arbitrator_pool: permissionless registry membership.
  • dispute() now snapshots which pool members are eligible (active, registered at least ARBITRATOR_ACTIVATION_LEDGERS ago) the moment the dispute is raised, before anyone could react to it existing.
  • select_arbitrator(): a separate, permissionless, one-shot call that draws a winner from that frozen snapshot after a short reveal delay. The result never depends on who calls it.
  • resolve() now requires the drawn arbitrator's authorization once a pool exists for that dispute, instead of admin/multisig.
  • refund_disputed_after_timeout(): permissionless full refund to the buyer if the arbitrator never resolves, mirroring the existing refund() timeout pattern.

Deployments with no arbitrators registered are unaffected — dispute()/resolve() behave exactly as before (all existing tests pass unmodified). No changes to htlc-core or atomic-swap.

Why this shape

This issue is scoped to selection mechanics; the on-chain arbitrator concept and dispute-timeout fallback it depends on (issue #275) hadn't landed yet, so this PR builds the minimal version of that foundation directly rather than blocking on it. resolve() stays binary (full to buyer/seller), matching its pre-existing behavior — partial-split resolution remains #275's scope, not duplicated here.

docs/arbitrator-pool-selection.md has the full design writeup, including the anti-gaming reasoning (activation delay, raise-time snapshot, one-shot draw) and why the underlying randomness source (env.prng(), consensus-seeded per Soroban's own docs) is unpredictable at the moment selection actually happens, plus an honest note on its limits (a corrupt validator could in principle bias it — out of scope for a contract-side mechanism).

Testing

  • New unit tests: activation delay gating, reveal delay gating, idempotent/caller-independent selection, resolve() requiring a completed draw, leave blocked while a dispute is pending, leave/rejoin resetting the activation clock, dispute-timeout refund, pool-size cap, and a uniformity/coverage simulation across many disputes.
  • All pre-existing tests (including the randomized property tests) pass unmodified.
  • Ran the full contracts-ci pipeline locally: cargo fmt --check, cargo build --workspace, cargo test --workspace, soroban-lint, and cargo build --workspace --exclude soroban-lint --target wasm32v1-none --release.

Test plan

  • cargo test --workspace (contracts)
  • cargo fmt --all --check
  • soroban-lint
  • wasm release build

Copilot AI review requested due to automatic review settings July 26, 2026 08:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@Tijesunimi004 is attempting to deploy a commit to the jotelfootball-tech's projects Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
velo Ready Ready Preview, Comment Jul 26, 2026 8:58am
velo-frontend Ready Ready Preview, Comment Jul 26, 2026 8:58am

Adds an opt-in arbitrator pool to the escrow contract, layered on top of
the single-arbitrator dispute foundation from Nullifier-Systems#275 (raise_dispute,
resolve_dispute, the single Arbitrator address, DisputeDeadline). When a
dispute is raised, arbitrator eligibility is snapshotted immediately
(active pool members registered for at least a day), and a winner is
drawn by a separate, permissionless, one-shot select_arbitrator() call
after a short delay. resolve_dispute() then requires that specific
arbitrator's authorization instead of the single Arbitrator, once a pool
exists for that dispute.

This closes the collusion risk in the single-arbitrator flow: neither
party can predict or influence who resolves their dispute, join/leave
timing around a specific dispute has no effect on its outcome, and an
unresponsive arbitrator can never freeze funds (reuses Nullifier-Systems#275's existing
DisputeDeadline/refund_after_dispute_timeout, with added bookkeeping to
free the drawn arbitrator's pending-dispute slot).

Deployments with no arbitrators registered are unaffected:
raise_dispute()/resolve_dispute() behave exactly as Nullifier-Systems#275 built them,
falling back to the single Arbitrator. No changes to htlc-core or
atomic-swap.

See docs/arbitrator-pool-selection.md for the full design rationale,
including why the underlying randomness source is unpredictable at the
point selection actually happens.
@Tijesunimi004
Tijesunimi004 force-pushed the feat/arbitrator-pool-selection branch from 555bb04 to 8bbf180 Compare July 26, 2026 10:50
@jotel-dev

Copy link
Copy Markdown
Contributor

fix conflict

@jotel-dev
jotel-dev merged commit 472da97 into Nullifier-Systems:main Jul 29, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Permissionless, collusion-resistant arbitrator selection

3 participants