Skip to content

feat: multi-oracle consensus for result verification (#956) - #1207

Merged
yahia008 merged 4 commits into
StellarCheckMate:mainfrom
Rayyanah0:feature/956-multi-oracle-consensus
Aug 3, 2026
Merged

feat: multi-oracle consensus for result verification (#956)#1207
yahia008 merged 4 commits into
StellarCheckMate:mainfrom
Rayyanah0:feature/956-multi-oracle-consensus

Conversation

@Rayyanah0

Copy link
Copy Markdown
Contributor

Summary

Closes #956 — Implement Multi-Oracle Consensus for Result Verification

A single Oracle is a single point of failure. This PR adds a consensus mechanism so match results require agreement from N-of-M independent Oracles before the payout executes.

What was changed

New public API

Function Description
add_approved_oracle(oracle) Admin registers an oracle for consensus
remove_approved_oracle(oracle) Admin deregisters an oracle
get_approved_oracles() Returns current approved oracle list
set_required_confirmations(count) Admin sets the threshold (default: 2)
get_required_confirmations() Returns current threshold
submit_result_consensus(match_id, winner, oracle_address) Any approved oracle votes on outcome; payout executes when threshold is met
get_oracle_confirmations(match_id) Returns current vote count for a match
is_allowlist_enforced() Exposes AllowlistEnforced state publicly

New error codes

Code Name Meaning
22 OracleAlreadyConfirmed Oracle already voted on this match
23 ConflictingResult Vote conflicts with prior oracle votes
24 NotEnoughConfirmations Threshold not yet reached
25 NotAnOracle Caller is not in the approved oracle list

Implementation notes

  • Approved oracles and the required confirmation count are stored in instance storage (shared config, survives TTL refreshes on every call).
  • Per-match vote records and confirmation counts use persistent storage with the standard MATCH_TTL_LEDGERS lifetime.
  • submit_result_consensus stores the first vote's Winner via the existing DataKey::OracleRecord key as the reference; all subsequent votes are checked against it.
  • The legacy submit_result path is unchanged. A new private execute_payout helper eliminates duplication.

Tests

26 new tests in contracts/escrow/src/tests/consensus.rs:

  • ✅ 2-of-2, 2-of-3, 3-of-3 happy paths — all trigger payout at exactly the threshold
  • ✅ Draw payout (stake returned to both players)
  • ✅ Partial votes do not trigger payout
  • ✅ Conflicting oracle vote → ConflictingResult
  • ✅ Duplicate oracle vote → OracleAlreadyConfirmed
  • ✅ Unapproved oracle → NotAnOracle
  • ✅ Paused contract → ContractPaused
  • ✅ Invalid match states (Pending, Completed, nonexistent)
  • ✅ Fund conservation (tokens preserved through consensus payout)
  • ✅ Confirmation count accuracy (get_oracle_confirmations increments correctly)
  • ✅ Escrow balance is zero after consensus payout
  • ✅ Active-match list is cleaned up after payout
  • ✅ Admin management (add/remove oracle, set/get required confirmations)
  • set_required_confirmations(0)InvalidAmount
  • ✅ Legacy submit_result still works independently

Pre-existing test fixes included

These 3 issues existed on main and were blocking the test suite from compiling or passing:

  1. events.rs: Missing Ledger trait import; test_expire_match_emits_event advanced to ledger 518_401 which causes persistent storage TTL expiry — fixed to use set_match_timeout(17_280) + advance to 17_281.
  2. token_allowlist.rs: Spurious & reference in assert_eq comparisons (type mismatch Address vs &Address).
  3. security.rs: test_accept_admin_wrong_caller_rejected expected Err(Ok(Unauthorized)) but Soroban panics with Abort on require_auth failure when the signer is absent from mock_auths.

CI

All 232 tests pass (cargo test -p escrow).

…uccess-callback

test: add test_transaction_signer_success_callback (StellarCheckMate#837)
…arCheckMate#956)

Add a multi-oracle consensus mechanism so that match results require
agreement from multiple independent Oracles before the payout executes.

## Changes

### contracts/escrow/src/errors.rs
- Add OracleAlreadyConfirmed (22): oracle already voted on this match
- Add ConflictingResult (23): oracle vote conflicts with prior votes
- Add NotEnoughConfirmations (24): threshold not yet reached
- Add NotAnOracle (25): caller is not in the approved oracle list

### contracts/escrow/src/types.rs
- Add DataKey::OracleConfirmations(u64): per-match confirmation count
- Add DataKey::OracleVote(u64, Address): per-(match, oracle) vote record
- Add DataKey::ApprovedOracles: instance-stored list of approved oracles
- Add DataKey::RequiredOracleConfirmations: configurable threshold (default 2)

### contracts/escrow/src/lib.rs
- Add add_approved_oracle(oracle): admin registers an oracle for consensus
- Add remove_approved_oracle(oracle): admin deregisters an oracle
- Add get_approved_oracles(): returns current approved oracle list
- Add set_required_confirmations(count): admin sets the threshold (default 2)
- Add get_required_confirmations(): returns current threshold
- Add submit_result_consensus(match_id, winner, oracle_address): any approved
  oracle calls this; payout executes once threshold is reached; conflicting
  votes are rejected
- Add get_oracle_confirmations(match_id): returns current confirmation count
- Add is_allowlist_enforced(): exposes AllowlistEnforced state publicly
- Extract execute_payout() private helper shared by submit_result and
  submit_result_consensus to eliminate duplication

### contracts/escrow/src/tests/consensus.rs (new)
26 new tests covering: happy-path 2-of-2 / 2-of-3 / 3-of-3 consensus,
draw payout, partial votes, conflicting votes, duplicate votes, unapproved
oracle, paused contract, invalid state (pending/completed/nonexistent),
fund conservation, confirmation count accuracy, escrow balance zero after
payout, and active-match list cleanup.

### Pre-existing test fixes
- events.rs: add missing Ledger trait import; fix test_expire_match_emits_event
  to use set_match_timeout(17_280) instead of advancing to ledger 518_401
  (which causes persistent storage TTL expiry in the test env)
- token_allowlist.rs: remove spurious & from assert_eq comparisons
- security.rs: update test_accept_admin_wrong_caller_rejected to expect
  auth-failure Abort rather than a contract Unauthorized return (Soroban
  panics on require_auth failure when the signer is not in mock_auths)

All 232 tests pass.
@yahia008

Copy link
Copy Markdown
Collaborator

hi fix conflict

# Conflicts:
#	Cargo.lock
#	contracts/escrow/src/errors.rs
#	contracts/escrow/src/lib.rs
#	contracts/escrow/src/tests/events.rs
#	contracts/escrow/src/tests/mod.rs
#	contracts/escrow/src/types.rs
@drips-wave

drips-wave Bot commented Aug 3, 2026

Copy link
Copy Markdown

@Rayyanah0 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@yahia008
yahia008 merged commit f153b51 into StellarCheckMate:main Aug 3, 2026
7 of 14 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.

Implement Multi-Oracle Consensus for Result Verification

2 participants