Skip to content

HF14: Prediction Markets - #124

Open
On1x wants to merge 123 commits into
masterfrom
pm
Open

HF14: Prediction Markets#124
On1x wants to merge 123 commits into
masterfrom
pm

Conversation

@On1x

@On1x On1x commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Introduces Hardfork 14 (HF14): Prediction Markets — a full on-chain
prediction-market subsystem for the VIZ blockchain, plus the read-only APIs,
wallet bindings, snapshot support, tests and documentation around it.

The feature is hardfork-gated (CHAIN_HARDFORK_14) and preserves the zero-sum
token invariant: prediction markets never mint or burn tokens, current_supply
is untouched, and settlement conserves value across bets, LP and forfeit pools.

What's included

Consensus / chain

  • Core PM protocol operations, objects and evaluators (markets, oracles,
    bets, liquidity, resolution, disputes, leverage) gated behind HF14.
  • Oracle rebuttals on disputes (pm_dispute_oracle_respond), early ban lift
    (pm_unban) and automatic ban expiry (pm_ban_expired virtual op via the
    per-block cron).
  • On-chain resolution statements on markets (decision_url/decision_reason)
    and dispute oracle responses, readable without history scans.
  • v5 chain properties for PM tuning (witness-median), including insurance
    coverage floors for listing (2.5x) and betting (1.5x, advisory).

Plugin / wallet / snapshot

  • prediction_market_api plugin with extensive read APIs: market/oracle/
    dispute queries, category taxonomy + live counts, klines, one-call enriched
    market view, and non-consensus leverage previews (quote/close/convert) that
    reuse the in-node margin math.
  • Wallet remote_node_api bindings for the PM read APIs.
  • Snapshot import support for all HF14 PM objects.

Docs & tests

  • EN + ru + zh-CN documentation (chain properties, plugin API, specification,
    operations, virtual operations) and workflow/integration design docs.
  • Comprehensive test_pm_lifecycle consensus-sim suite covering the full
    market lifecycle, disputes, leverage, bans and settlement invariants.

Notes

  • Base branch: master. Feature branch: pm (16 commits).

On1x added 24 commits June 16, 2026 19:33
…ront-running protection

- Introduce two optional execution modes per bet: batch (mode 1) and commit-reveal (mode 2)
- Preserve instant per-bet execution (mode 0) as default for best UX
- Define global and per-market parameters for configuration and governance control
- Implement batch epochs with unified uniform-price settlement to prevent intra-batch front-running
- Add commit-reveal operations: bet commitment, reveal, penalty for no-reveal with transfer into winners' pool
- Design batch settlement math preserving liquidity provider invariant and fairness via canonical CPMM aggregation
- Add database schema changes for bets, commitments, and market flags supporting new modes and states
- Extend market logs to track batch-related actions: commit, reveal, batch settlement, and forfeits
- Specify layered defenses against front-running including epoch snapshot pricing and min_tokens enforcement
- Define phased rollout plan: batch mode first, then commit-reveal, followed by tiering and client policies
- Document open questions and future improvements like encrypted sealed bids (mode 3) and cancellation policy
…off strategy

- Add new chain_properties versions 4 (hf13) and 5 (hf14 PM) for governance params
- Introduce 40+ Prediction Market consensus parameters including fees, disputes, batch settings, cron budget, lazy pool, and leverage
- Define market fee structure and governance cap rules focusing on oracle fee cap
- Document versioning, median calculation, and validator publishing rules for new params
- Implement leverage fund as sub-allocation of lazy pool free balance with detailed accounting and protocol state transitions
- Present full mathematical leverage model: CPMM/LMSR calculations, cancel values, liquidation thresholds, safety margin, and leverage constraints
- Describe atomic liquidation mechanism ensuring pool protection before opposing bets execute, including cascade logic and cascade loop handling
- Analyze risk types and mitigation: price-movement risk elimination via atomic liquidation, safety margins, and dynamic caps; outcome risk remains inherent
- Provide detailed architecture overview, fund allocation parameters, frontend UI terminology (Boost vs Leverage), and risk analysis
- Outline protocol operations, API endpoints, database schema, pre-calculation and slider logic for leverage positions
- Address MEV considerations for liquidation rebalancing and sandwich attacks with mitigation plans for VIZ DLT implementation
…ion by role

- Add comprehensive README outlining canonical scenarios for normal and disputed resolves
- Document master ledger calculations for payout distributions and zero-sum properties
- Include role-specific subfolders with interaction diagrams and signed/virtual operations
- Provide detailed token flow tables for normal and disputed market outcomes
- Outline leverage mechanics including open, close, liquidation, and settlement steps
- Describe dispute scenarios: disputer wins, loses, and forced auto-close with penalties
- Verify all operations and virtual operations present in code with references
- Add instructions on how to observe states and events via API plugin methods
- Cover edge cases like time penalties, liquidations, and refund mechanics
- Provide an index of workflow document folders by participant role for easy navigation
- Updated fc submodule pointer from 99b5d133 to 5a9d84a1
- Ensured thirdparty dependencies are current and consistent
- Added mermaid package version 11.4.1
- Added vitepress-plugin-mermaid version 2.0.17 for Mermaid integration
- Added multiple new dependencies related to mermaid and diagram rendering
- Included types packages for d3 and related libraries for better type support
- Updated package-lock.json to reflect new dependencies and their versions
- Removed several optional and deprecated dependencies to clean up lock file
- Increase total number of hardforks from 13 to 14
- Introduce hardfork 14 with features for prediction markets (binary CPMM, multi LMSR)
- Add oracles, dispute mechanisms, batch/commit-reveal, and lazy liquidity pools
- Define placeholder activation times for mainnet and testnet
- Use version 4.0.0 for hardfork 14 release candidate
- Add chain_properties_pm median evaluator and integrate into median calculation
- Implement lazy-pool logic for DAO-committee voting weight in committee_processing
- Register PM evaluators for all PM operations in database initialization
- Add core indexes for PM-related objects for consensus and chain state tracking
- Implement PM liquidity settlement, market resolution, leverage liquidation, and recall mechanics
- Add HF14 hardfork initialization including lazy-liquidity pool singleton creation
- Update CMakeLists.txt to include PM source and header files with proper compiler flags
- Enhance database.cpp with PM processing hooks and vote weight adjustments for lazy pool stake
- Provide detailed internal helpers for PM operation including settle market, refund bets, and liquidity allocation
…ive APIs

- Implement prediction_market_api plugin with full lifecycle management
- Provide APIs to query markets, outcomes, bets, positions, and liquidity
- Support account leverage positions and creator ban status retrieval
- Offer oracle info and list oracles with reliability scoring
- Include dispute info and vote tallying with projected verdict calculation
- Record and prune metadata and kline time-series data on block application
- Add support for lazy pool and deposit queries along with chain properties
- Implement metadata parsing and filtered market listing by category and jurisdiction
- Integrate with chain plugin database and handle post-operation kline recording
- Configure plugin with pmm-ttl-days option for metadata retention days
- Setup CMake build configuration for prediction_market_api plugin library
- Introduce functions to import pm_oracle, pm_market, pm_outcome, and pm_dispute objects with
  shared_string member handling
- Add export and import logic for all HF14 PM related indices in snapshot processing
- Clear existing PM objects before importing new ones during database initialization
- Enhance snapshot deserialization to handle absent PM objects in pre-HF14 snapshots
- Log import counts for each PM object type during snapshot loading to aid diagnostics
- Include prediction_market_api plugin in wallet build dependencies
- Add remote_prediction_market_api binding with optional connection handling
- Implement pm_api() accessor for prediction_market_api proxy with assertion
- Introduce prediction market helper methods for oracle registration, update, market creation,
  bet placement, commitment hashing, bet commit/reveal/cancel, liquidity management, market resolution,
  dispute creation/voting/resolution, position transfer, lazy deposit/withdraw, and leverage operations
- Add read API passthrough methods for markets, oracles, bets, positions, liquidity, disputes, lazy pool,
  chain properties, market metadata, and market kline data
- Extend fc::api remote_node_api.hpp with prediction market API message signature class and FC_API definition
- Update wallet.hpp and wallet.cpp with full prediction market API support and method declarations
- Ensure wallet starts normally even if prediction_market_api plugin is unavailable on connected node
- Include prediction_market_api in CMakeLists.txt for vizd program
- Add prediction_market_api header inclusion in main.cpp
- Register prediction_market_api plugin in appbase application initialization code
…ests

- Add tests covering oracle registration after HF14 activation
- Implement full binary market lifecycle: creation, betting, resolution, payout
- Test committee dispute scenario with outcome overturning via voting
- Verify lazy-pool stake contributes to dispute voting weight and quorum
- Ensure external oracle rejection refunds seed liquidity exactly once
- Add bet cancellation scenario reversing CPMM reserves and bets sum
- Adjust consensus_sim harness to support new pm tests and sanitizer flags conditionally
- Expose direct database access in simulated_node for test assertions
- Fix simulated_node block witness field to validator for accuracy in tests
…operties support

- Introduce HF13 distribution epoch length and HF14 prediction markets features in governance docs
- Add detailed median-voted HF14 prediction-market parameters and kill-switch flags explanation
- Add new `prediction_market_api` plugin with extensive JSON-RPC read-only methods for markets, bets,
  oracles, disputes, lazy pool, and governance data access
- Include computed DTOs and charting support for prediction markets with offset-from-newest pagination
- Document prediction market concepts analysis comparing Onix protocol to theoretical models
- Update advanced hardfork and chain properties docs to cover new prediction market functionality
- Introduce readonly JSON-RPC plugin for HF14 prediction markets state access
- Document market-related API methods including markets, outcomes, bets, liquidity, and metadata
- Describe position, leverage, oracle, dispute, lazy pool, and governance methods
- Provide details on kline/time series for market weight history and pagination approach
- Explain computed DTOs representing bets, oracles, votes, and payout structures
- Include example usage and code snippets for API calls and data processing
- Link to relevant protocol operations and chain property documentation

docs(governance): update chain properties with HF13 and PM parameters

- Add chain_properties_hf13 with distribution_epoch_length parameter
- Introduce chain_properties_pm (v5) for ~30 prediction market parameters and kill-switch flags
- Detail all median-voted parameters for oracle, market, batch, dispute, time penalty, lazy pool, leverage, and fairness
- Clarify live kill-switch flags to disable commit-reveal, lazy pool, or leverage without hardfork

docs(advanced): extend hardfork management with HF13 and prediction markets

- Add entries for HF13 epoch length and HF14 prediction markets including CPMM/LMSR, oracles, disputes, commit-reveal, lazy-pool, and chain properties v5

docs(prediction-markets): add comprehensive analysis of conceptual mapping of Onix PM protocol

- Provide detailed table comparing 90 theoretical prediction market concepts against VIZ Onix on-chain implementation
- Categorize concepts as solved, inherent, not needed, partial/roadmap, client layer, or open risks
- Discuss information theory, mechanism design, liquidity and trading aspects in depth
- Highlight Onix innovations: risk-free LP, CPMM binary, LMSR multi, commit-reveal batch bets, optional leverage subsystem, lazy pool governance voting weight
- Explain architectural decisions omitting orderbooks, combinatorial markets, and peer prediction
- Updated chainbase submodule commit from 39ab2c2 to d429230
- Ensures third-party library is aligned with latest upstream changes
…cycle, coverage floors and thin-client APIs

Consensus (HF14 follow-up ops, appended so operation indices stay stable):
- pm_dispute_oracle_respond (op 22): the market oracle posts a public rebuttal
  onto an open dispute; stored on the dispute object (public-hearing model),
  allowed only while open and within oracle_response_deadline, re-post overwrites.
- pm_unban (op 23): the resolver that imposed an account-mode ban (banned_by)
  may lift it early; sets banned_until to epoch and clears banned_by.
- pm_ban_expired (virtual): the per-block cron sweeps temporary oracle/creator
  bans at banned_until, clears them and emits the lift for history/indexers.

On-chain state:
- pm_market gains decision_url/decision_reason — the oracle's resolution
  statement stored on-chain (set by pm_resolve_market / pm_no_contest reason),
  readable via get_market with no history scan.
- pm_resolve_market_operation gains decision_reason (reflected on the wire).
- pm_dispute gains oracle_response/oracle_response_time.
- pm_oracle and pm_creator_ban gain banned_by; pm_creator_ban gains a
  by_ban_expiry index so the cron sweeps expired bans oldest-first (cleared
  bans sort into the 0-bucket, permanent bans past now, both skipped).

Chain properties (witness-median tunables):
- pm_listing_min_coverage_percent (2.5x): hide under-insured markets from the
  default catalog (enforced by the API plugin, revealed via show_risky).
- pm_betting_min_coverage_percent (1.5x, advisory): client risk-confirm
  threshold; validated betting <= listing.

Thin-client read APIs (non-consensus, for the viz-js client):
- get_leverage_quote / get_leverage_close_preview / get_leverage_convert_preview
  reuse the frozen pm::leverage math to mirror the open/close/convert evaluators.
- get_market_categories (taxonomy + live counts), get_market_full (one-call
  enriched, account-scoped), get_lazy_allocations / get_market_lazy_allocation.
- Wallet remote_node_api bindings for all of the above.

Docs & tests:
- EN + ru + zh-CN docs updated (chain-properties, prediction-market-api,
  specification, operations overview/prediction-markets/validators,
  virtual-operations); library-integration spec + thin-client plan added.
- test_pm_lifecycle: cases #58-#63 cover oracle rebuttal + decision_reason,
  no-contest rationale, manual unban and its guards, and ban auto-expiry vop.
…throughs

The cli_wallet build failed because remote_prediction_market_api and the
wallet_api pm_get_*/pm_list_* methods returned the node's typed objects. Those
chainbase state objects (pm_market_object, pm_bet_object, ...) and the API DTOs
embedding them are not default-constructible (deleted default ctor / shared_string
members require a segment manager), so fc::api's client deserializer (T tmp;
var.as<T>()) could not instantiate them.

Return fc::variant instead: the node already emits fully-formed JSON and cli_wallet
prints the variant unchanged, so the read surface is identical.
…te_node_api

cli_wallet failed to compile because remote_node_api.hpp pulled in
<graphene/plugins/prediction_market_api/prediction_market_api.hpp> transitively,
but programs/cli_wallet has no include path to that plugin. After the read
pass-throughs switched to fc::variant, the header (and the pmapi alias) are no
longer referenced anywhere in the wallet, so remove them. graphene_wallet still
builds; the public wallet header no longer leaks a plugin-only dependency.
…LP fee

Add two HF14 median-voted consensus parameters and their enforcement:

- pm_oracle_accept_window_sec (default 1h): a pending market the named
  oracle never accepts nor rejects is voided by the per-block cron once
  now >= created_time + window. The creators seed liquidity is refunded
  (return_liquidity); the non-refundable creation fee stays with the DAO
  fund. Tracked via a new pm_market_object.accept_deadline field and a
  by_accept_deadline index; emits the new pm_market_expired virtual op
  (op-id 101, appended last in the operation variant to keep tags stable).

- pm_lazy_min_liquidity_fee_percent (default 2%): the lazy pool skips
  markets whose liquidity_fee_percent is below this reward floor, so it
  never subsidizes depth it is not paid enough to provide.

Wired into calc_median and chain_properties_pm::validate().
… fee

Cover the new pm_oracle_accept_window_sec / pm_market_expired lifecycle
and the pm_lazy_min_liquidity_fee_percent reward-floor gate across:

- EN docs (chain-properties, specification, operations, virtual-operations)
- RU and zh-CN localizations (@l10n) at full parity with the EN source
- library integration spec (delta section + property/vop tables, op-id 101)
  and thin-client plan
- Onix paper EN + RU (state machine, acceptance flow, lazy-pool gate);
  PDFs rebuilt via pandoc + xelatex (EN 30pp, RU 32pp, 0 missing glyphs).
- Added warning that the live protocol uses basis points (bp), not permille (‰)
- Explained the conversion from original PHP prototype’s permille to bp in on-chain code
- Specified that all fee fields (oracle_fee_percent, creator_fee_percent, liquidity_fee_percent, etc.) use bp (10000 = 100%)
- Highlighted the use of `fromBP` parser for fee fields and rejection of markets exceeding fee sum 10000
- Warned that using deprecated `fromPermille` leads to incorrect fee values, off by a factor of 10
…line

The ?: between time_point_sec() and (now + fc::seconds(...)) has no common
type — the latter yields fc::time_point, and each type converts to the other,
which GCC rejects as ambiguous. Wrap the second branch in an explicit
time_point_sec(), matching the copy-init conversion already used for the
reveal/dispute deadlines in this file.
…erations

The generic impacted-account visitor only collected signing authorities, so
prediction-market events were missing from the histories of accounts that did
not sign them:

- signed ops lost their counterparties (pm_create_market -> oracle,
  pm_transfer_position -> recipient, pm_unban -> target, oracle auto-accept
  whitelist);
- virtual ops carry no authority at all, so payouts, forfeits, liquidations,
  oracle penalties, market accept/expire and ban expiry were invisible to the
  affected users.

Add explicit get_impacted_account_visitor overloads for the PM user and
virtual operations, inserting every account field they carry. Market-only
virtual ops that reference a market by id but carry no account name
(pm_batch_settle / pm_dispute_finalize / pm_dispute_auto_close /
pm_lazy_recall) are intentionally left to the generic handler.
@On1x

On1x commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Addressed the account-history review blocker in 3eebfd6: added explicit get_impacted_account_visitor overloads for the PM user and virtual operations (counterparties on signed ops + all account fields on virtual ops, which have no signing authority). Market-only virtual ops that carry no account name (pm_batch_settle / pm_dispute_finalize / pm_dispute_auto_close / pm_lazy_recall) are intentionally left to the generic handler.

…, per-node)

The free-form `metadata` JSON was stored in the consensus `pm_market_object`
(shared_string) permanently — never pruned — even though consensus never reads
it (it is written once and only parsed off-chain by the prediction_market_api
plugin). That let a market permanently bloat every node's chainbase/shared
memory with unbounded, unvalidated data.

Move it out of consensus entirely:

- pm_market_object: drop the `metadata` field (member, ctor, FC_REFLECT). The
  operation `pm_create_market_operation.metadata` is unchanged — clients still
  send it and it lives in the block log, exactly like custom_operation.json.
- pm_create_market_evaluator: stop persisting metadata into state.
- prediction_market_api: ingest metadata off-chain from the create operation
  (post_apply_operation) into the existing prunable pm_market_meta_object,
  instead of reading it back from the consensus object in on_block.
- snapshot: drop the metadata import/export for pm_market (auto-excluded from
  the reflected dump; import of legacy snapshots ignores the field).

Because it is now non-consensus, each node prunes it on its own schedule via
--pmm-ttl-days (default lowered 7 -> 5; 0 keeps it forever for archival nodes).
No consensus length/UTF-8 cap is needed — the blob no longer touches state.
@On1x

On1x commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Addressed the unbounded on-chain metadata review blocker in 5479b9a (design agreed with maintainer): the free-form pm_create_market.metadata is no longer persisted in the consensus pm_market_object at all — consensus never reads it, so it moved off-chain into the prediction_market_api plugin, ingested from the create operation into the already-prunable pm_market_meta_object. Consequences: no permanent chainbase bloat, no consensus length/UTF-8 cap needed (the blob never touches state; it lives in the block log like custom_operation.json), and each node prunes it on its own schedule via --pmm-ttl-days (default lowered 7→5; 0 = keep forever for archival nodes). Note: plugin metadata index is now built from the operation stream, so enabling the plugin on an existing chain requires a replay to backfill (standard for plugin indexes). Not compiled locally (no build env here) — CI/build to confirm.

…xed retention

A resolved+settled market (status 3, payout_status 3) is immutable — no betting,
dispute, resolve or payout can touch it again; it only lingered in chainbase
"for history", growing shared-memory state without bound.

process_pm_markets() now GCs such markets and their whole object cluster
(outcomes, bets, liquidity, commits, dispute votes, leverage positions, the
dispute and lazy-allocation rows) once they have been closed for a FIXED protocol
constant PM_CLOSED_MARKET_RETENTION_SEC = 5 days (measured from
result_expiration + dispute grace). The retention is hardcoded and identical on
every node, so pruning is fully deterministic: every node deletes exactly the
same markets at the same block, keeping shared-memory state and snapshots in
lock-step network-wide (a node syncing from a snapshot ends up with the same
market set as everyone else). Work is bounded by the existing per-block cap.

Only status-3/payout-3 markets are collected; disputed (payout_status 2) and
never-settled markets are left untouched. Nothing holds an id-reference to a
settled market, so there are no dangling references after removal.
@On1x

On1x commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Follow-up (31e8aab): resolved markets are now garbage-collected from consensus state. A settled market (status 3 / payout_status 3) is immutable, so process_pm_markets() reclaims it and its whole object cluster (outcomes/bets/liquidity/commits/dispute-votes/leverage/dispute/lazy-allocation) after a FIXED PM_CLOSED_MARKET_RETENTION_SEC = 5 days (from result_expiration + grace). Retention is a hardcoded protocol constant — identical on every node — so deletion is fully deterministic: all nodes prune the same markets at the same block and snapshots stay in lock-step (a node syncing from a snapshot gets the same market set as everyone else). Bounded by the existing per-block cap; only status-3/payout-3 markets are touched (disputed = payout_status 2 are excluded). Not compiled locally (no build env) — needs CI + ideally a consensus_sim case.

…ones

Extend the market garbage collector to reclaim ANY dead market a fixed 5 days
after it becomes terminal — not only resolved+paid ones. A market is dead once
nothing can act on it: resolved and paid out, void/no-contest, oracle-rejected,
or the oracle never accepted and the accept window expired.

To anchor the retention on the actual moment of death (rather than the declared
result_expiration), add a `finalized_time` field to pm_market_object, set to the
head-block time at every terminal transition:
  - oracle rejects the market (status -1)
  - accept window expires, market voided (pm_market_expired)
  - oracle misses resolution, refund (pm_oracle_missed_penalty)
  - dispute auto-close refund
  - settlement / auto-payout (covers resolved, no-contest, post-dispute)

A new by_finalized index (finalized_time, id) lets process_pm_markets() sweep
terminal markets in time order, skipping the finalized_time==0 live bucket, and
delete each cluster PM_CLOSED_MARKET_RETENTION_SEC (5 days) later. Retention is a
fixed protocol constant identical on every node, so pruning stays deterministic
and snapshots identical network-wide. Snapshot import reads finalized_time when
present. Work stays bounded by the per-block cap.
import_pm_oracles() previously skipped the oracle-metrics fields added
by the P1-P5 commits (markets_in_dispute_window, disputes_awaiting_
response/decision, resolved_late_count, resolution_time_hist), so a
snapshot taken after the upgrade lost them on import (gauges were
re-derived by pm_seed_oracle_gauges only when dgpo.pm_oracle_gauges_
seeded was absent, and the forward-accumulating histogram/late counter
were never recoverable).

Forward-compatible import: fields are read only when present, so old
snapshots (pre-upgrade) still import cleanly and keep the existing
seed-on-first-block behavior; new snapshots preserve the gauges and the
resolution-latency histogram.

Display-only; no consensus impact. Matches the fc::array<share_type,8>
serialization used by FC_REFLECT (base64 vector<char> via from_variant).
avg_resolution_time (uint32) * (n-1) + rt can reach the uint64 boundary
once n approaches the uint32 max (product of two uint32 values is within
a hair of 2^64, and adding rt can tip it over). Accumulate the running
mean in fc::uint128_t to keep the counter exact.

Mathematically equivalent to the previous integer formula; verified with
a quick script across normal and boundary cases (matches on all).
On1x and others added 16 commits August 6, 2026 18:58
Add an on-time resolution ratio as a 4th reputation factor in the
display-only reliability_score. Late-but-delivered resolves increment
markets_resolved and previously earned full accuracy credit, so a
chronically-late oracle scored identical to a punctual one; the
existing resolved_late_count (P5) now feeds the score.

timely = (markets_resolved - resolved_late_count) / markets_resolved,
optimistic 10000 until anything is resolved, clamped late<=resolved.
Rebalanced blend: accuracy 40% . verdicts 30% . responsiveness 15% .
timeliness 15% (max -1500bp for an always-late oracle). Non-consensus,
bp 0..10000, clients unaffected. avg_resolution_time stays out (latency
from betting close, not deadline overrun).
… v2.1)

Bring the API-plugin reference and the spec in line with the shipped
oracle-metrics work on this branch:

- prediction-market-api.md: add list_markets_in_dispute_window and
  list_oracle_disputes; rewrite the pm_oracle DTO to document the
  reliability_score v2.1 four-factor blend, the stored workload gauges,
  and the computed-on-read awaiting/oldest-age and p50/p95 latency reads.
- specification.md section 14: replace the superseded 0-100 composite
  (volume/experience tiers, freshness, trust_score) with the actually
  implemented v2.1 bp score (accuracy 40 / verdicts 30 / responsive 15 /
  timely 15, decayed penalty stamps, ban docks, confidence shrink); add
  a Workload Gauges & Latency Telemetry subsection; expand the raw-metrics
  table to the real reflected field names and the new gauges/counters;
  add on_time_rate; note the pm_dispute_opened vop (P1).

Docs only.
…-import

fix(snapshot): import pm_oracle oracle-metrics fields (P1-P5)
…t128

fix(pm): compute avg_resolution_time running mean in uint128
…bury a good oracle at 0

penalty_stamps is a lifetime cumulative counter (one per zero-volume
resolution, pm_evaluator §4.10). On a busy/seeded oracle it reaches tens of
thousands (testnet polymarket = 65183), and reliability_score charged the RAW
count x300bp -> ~19.5M bp deduction -> score floored to 0 even though accuracy,
verdicts, responsiveness and timeliness were all ~perfect.

Cap the effective stamps at 4, exactly as the lazy-allocation consumer already
does (pm_evaluator ~line 740), so both readers of penalty_stamps agree and the
fault-stamp ding is bounded to 1200 bp. Display-only / non-consensus (API
plugin), no HF impact. Single-TU -fsyntax-only clean.

After this, testnet polymarket reliability ~8793 bp instead of 0.
… grief disputers

pm_resolve_market shifted result_expiration EARLIER only (if now < result_expiration).
That let a griefing oracle stall until ~result_expiration + pm_dispute_grace_sec and
report only then: the dispute deadline (result_expiration + grace) was already spent, so
disputers got a near-zero or zero window. Owner flagged the hole.

Fix: set result_expiration = now unconditionally on resolve (early OR late), exactly as
pm_no_contest already does. The dispute/settle/LP-lock grace now ALWAYS starts from the
announcement, so disputers always get the full pm_dispute_grace_sec regardless of report
timing. The settle wait extends by however late the report was — the necessary price of a
fair, always-full dispute window. late/rt telemetry is captured pre-shift, so
resolved_late_count and latency stats stay honest. Spec + comments updated. Single-TU clean.
…first

User-facing activity feeds surfaced oldest positions first (id asc), so a
user's latest bets sat below months-old ones. Add an optional order arg
(default "newest", id desc via reverse equal-range traversal; "oldest"
keeps the legacy order) to get_account_positions,
get_account_leverage_positions, get_market_bets, list_markets_by_creator
and list_markets_by_oracle. Oracle work-queue methods (awaiting_resolution,
in_dispute_window, by_oracle_status) intentionally stay oldest-first —
a queue is worked longest-waiting first.
… sort for by_oracle/by_creator

A) list_markets_by_category gains an optional status arg (default -1 = any)
so an "active only" browse pages accurately server-side instead of the
client over-fetching and dropping rows locally.

B) list_markets_by_oracle / list_markets_by_creator accept order=volume
(bets_sum desc) and order=expiration (soonest-closing first, open-ended
last) in addition to newest/oldest, so oracle/creator profile pages can
offer the same sort chips as category browse. Shared page_markets_sorted
helper materializes the account's market set and sorts it, mirroring the
volume/expiration path list_markets_by_category already uses.
The oracle profile's Active/Resolved market tabs are served by this method
and defaulted to oldest-first — the same stale-first problem just fixed
elsewhere. Add an optional order (newest default, plus oldest/volume/
expiration via page_markets_sorted) so profile tabs lead with the latest
markets and can offer volume/ending sort chips. Work-queue methods
(awaiting_resolution / in_dispute_window) are separate and unchanged.
…cessing cap

process_pm_markets() section 6 (batch epoch settle) walked every active
allow_batch market in by_status (status, id) order and consumed the
per-block pm_processing_cap_per_block budget (done++ and
current_epoch++) for EVERY market, even ones with zero queued bets.
With more active allow_batch markets than the cap (e.g. 50k+ polymarket
mirror markets on testnet vs cap=200), the loop stopped after the ~cap
oldest markets and never reached newly-created markets: their revealed
commit-reveal bets stayed queued (status=5) forever and current_epoch
never advanced.

Fix: only advance current_epoch and consume the cap for markets that
actually had queued (status=5) bets this epoch. Idle markets are skipped
without touching done, so the scan can reach newly-created markets and
their bets settle on the next epoch boundary. Existing behavior for
markets with queued bets is unchanged (slippage refunds still count as
work, so had_queued covers both settled and refunded paths).
…sign spec (F1/steemit#300)

Foundation for the deferred outcome-contingent early-exit claim mechanism:
early exits (bet cancel / leverage close) will no longer extract curve value
from LPs; their outcome-contingent profit is paid at settlement from a bounded
slice (default 33%) of the losing pool, FIFO by exit time. New median-voted
validator param bounds that slice. Struct + validate + FC_REFLECT (append-only)
+ calc_median wired; single-TU -fsyntax-only clean. Mechanism (object, evaluator
rewiring, settlement distribution) follows. Spec: docs/prediction-markets/early-exit-deferred-claim.md
New consensus object for outcome-contingent early-exit claims: object_type enum
(append-only) + fwd decl + id_type, struct with {market, account, kind, outcome_index,
claim_amount, exit_time} and by_id / (market,id) FIFO indices, FC_REFLECT, add_core_index,
and snapshot export/import (forward-only, no seed → must be serialized). Also reflects the
previously-unlisted pm_lazy_withdraw_request_object_type in FC_REFLECT_ENUM (clears -Wswitch).
Additive/inert — no evaluator wires it yet. Single-TU -fsyntax-only clean (database, pm_evaluator,
snapshot). Next: cancel_bet/leverage-close record claims + settlement FIFO distribution.
…nert)

settle_market pays outcome-contingent deferred claims BEFORE the parimutuel split,
from a bounded slice (pm_early_exit_reward_cap_percent x losers_sum) of the losing pool,
FIFO by exit order (by_claim_market), winning outcome only; unfunded = haircut; unused
slice flows to winners; every market claim consumed. paid_claims folded into forfeit_pool
so winners_pool drops by exactly the paid amount (>= (1-cap)*losers - fees >= 0: no
uncovered mint, no LP hit). Conservation validated by standalone model (6 scenarios,
IN==OUT + winners_pool>=0). Inert until the exit paths record claims. single-TU clean.
Next: exit side (cancel_bet + liquidate_position + pm_leverage_close) records claims;
void handling; dedicated claim-paid vop + read API.
…money logic closed

- cancel_bet (binary): refund=min(curve_refund,stake); profit tail -> pm_deferred_claim
  {kind=0,outcome=side}; residual>=0 -> forfeit (never negative).
- liquidate_position: pool takes obligation; residual (cv-pool_received) -> claim
  {kind=1,outcome} instead of immediate pay; forfeit += total_bet-pool_received.
  reason 4 = terminal void/no-contest -> refund immediately (old path), forfeit += total_bet-cv.
- force_close_positions(reason=2 default); settle_market passes 4 on win<0, return_liquidity
  passes 4; both void paths purge_deferred_claims (no winning outcome -> claims worthless).
- pm_leverage_close (voluntary): residual -> claim, forfeit += total_bet-obligation.
Conservation validated by standalone model (6 scenarios, IN==OUT, winners_pool>=0, no mint,
LP untouched). single-TU -fsyntax-only clean. TODO: claim-paid vop + read API; libs; UI; article.
…emit#300)

The settlement distribution loop credited early-exit deferred claims with a bare
adjust_balance, leaving no account-history trace and no way for indexers/clients to
reconcile the settlement's token flow. Add a dedicated virtual op and a read API:

- pm_early_exit_claim_paid_operation {account, market_id, kind, outcome_index,
  claimed, paid}: appended at the END of the operation variant (existing op-ids stay
  stable), FC_REFLECT'd, and emitted next to the adjust_balance in settle_market.
  claimed vs paid exposes any bucket-exhaustion haircut. Routed to the early-exiter's
  account history via the account_history impacted-accounts visitor.
- get_deferred_claims(market, [from=0], [limit=100]): a market's pending
  outcome-contingent claims in FIFO exit order (by_claim_market); empty once the
  market settles (claims consumed). Plugin-only, like get_lazy_withdraw_requests —
  clients call via rawApi/JSON-RPC, no wallet wiring.

All three touched TUs pass single-TU -fsyntax-only. Spec checklist updated.
…#139)

Section 6 of process_pm_markets() after #139 no longer let idle markets eat
the processing cap, but two gaps remained:

- The scan still paid the LMSR q-vector snapshot per idle market before
  detecting idleness. Probe the bet index first: an idle market now costs one
  lower_bound (bets at a market's current epoch are queued-only, so an empty
  probe means idle).
- The scan always restarted at the lowest id, so >= cap always-busy low-id
  markets could permanently starve newer ones (cheap griefing: dust batch
  bets on the oldest cap markets every epoch). Persist a round-robin cursor
  in dynamic_global_property_object (pm_batch_settle_cursor) and resume the
  scan there, wrapping once per boundary.

The market by_status index becomes ordered_unique (status, id) so the cursor
can seek mid-range and in-status iteration order is deterministic by id; all
existing users do prefix lookups (scalar lower_bound / equal_range) and are
unaffected. Index + dgpo field changes require a chain reset — the pm branch
testnet is being reset for #139 anyway.

Regression tests (consensus_sim, BUILD_CONSENSUS_TESTS):
- batch_settle_idle_markets_do_not_starve: cap=1, three allow_batch markets,
  bet only on the newest — guards the #139 fix (fails on pre-#139 code).
- batch_settle_round_robin_prevents_busy_starvation: cap=1, market 0 fed a
  fresh queued bet every epoch — guards the cursor (fails without it).
On1x and others added 2 commits August 8, 2026 15:28
fix(pm): round-robin batch-epoch settle scan + idle fast-path (follow-up to #139)
…arantee

Long-lived leverage positions accrue funding_paid unbounded in
accrue_leverage_funding, so obligation (liquidation_threshold +
funding_paid) can exceed total_bet. pot_retained = total_bet - obligation
then goes negative, pushing forfeit_pool below zero and manufacturing an
`uncovered` shortfall (LP principal hit / mint) at settlement — defeating
the F1/steemit#300 no-uncovered-by-construction guarantee.

Clamp pot_retained at 0 in both pm_leverage_close and liquidate_position.
The pool still recovers its obligation from cv (reserves); the market pot
simply never goes negative, so winners_pool >= (1-cap)*losers - fees >= 0
holds unconditionally.
On1x added 5 commits August 8, 2026 20:29
fix(pm): clamp leverage pot_retained at 0 to preserve no-uncovered guarantee
…m loop (steemit#349)

Adversarial follow-ups to the F1/steemit#300 early-exit review (owner-approved bundle),
riding the same pm chain-reset as F1/#139/#140/#141.

steemit#348 (defense-in-depth, closes the remainder of goal steemit#290): F1 guarantees
`uncovered == 0` by construction, but #141 showed a regression can break it and it
was charged to LP principal SILENTLY (the only diagnostic was in the no-LP subcase).
Add an always-on elog in settle_liquidity whenever uncovered > 0, regardless of LP
presence, so any future regression is loud in node logs / acceptance instead of
eroding LP principal unseen. Log-only: deterministic, no consensus effect, no halt
(a hard assert would take the chain down on an unforeseen edge — worse than charging
LP and logging).

steemit#349 (DoS bound): settle_market's early-exit claim-distribution loop iterated every
deferred claim on a market with no cap — a griefer could spam cancels/leverage-closes
to force unbounded per-block work when the market settles (same class as #139/#140).
Add MAX_PM_DEFERRED_CLAIMS_PER_MARKET (10000) and a monotonic `deferred_claim_count`
on pm_market_object; past the cap an early-exit skips recording its contingent claim.
Conservation-safe: the skipped tail simply stays in the curve and pays 0 at settlement,
exactly like bucket-exhaustion — NOT routed to forfeit_pool (that would double-count the
tail, which is still in the curve → a mint). The exit itself always succeeds; only the
contingent upside is forgone past the cap. Field: FC_REFLECT appended (order stable),
snapshot export via reflection, import contains-guarded so it survives reimport.

All touched TUs pass single-TU -fsyntax-only.
…Theorem 2)

The early-exit reward cap (pm_early_exit_reward_cap_percent, default 33%) bounds
paid_claims to cap*losers_sum, but that alone does NOT guarantee solvency: per-market
fees are capped only by oracle+creator+liquidity <= 100% at creation (no chain param),
so a valid market can push fees near 100% of losers_sum. Combined with the default
cap this makes fees+claims exceed the losing pool; compute_settlement then floors
winners_pool at 0 and charges the shortfall to LP principal (uncovered, F1) -
reachable with VALID default params, not just extreme governance medians.

Clamp the bucket to the settlement headroom (avail - fixed_paid + forfeit_pool ==
winners_pool before claims), mirroring parimutuel.cpp fee math exactly. paid_claims
can no longer drive winners_pool negative, so the no-uncovered / no-mint / LP-safe
guarantee holds unconditionally (not only under a fees+cap<=100% governance invariant).
The clamp only ever reduces the bucket, so it is strictly more conservative.

Found by adversarial self-review while formalizing Theorem 2 for the Onix paper.
…U PDFs

- add Section 6.8 (Early Exit via Deferred Outcome-Contingent Claims),
  Theorem 2 (Bounded Early Exit) and Corollary 3 (no-mint / LP-safe /
  loser-never-profits) to both EN and RU whitepapers
- update abstract, contributions and the leverage subsection (6.6) to
  reconcile with the deferred-claim mechanism
- regenerate onix-protocol-paper.pdf (34 pp) and -ru.pdf (42 pp) from the
  updated markdown (pandoc + XeLaTeX)
- onix-protocol-paper-ledger.pdf (EN, 27 pp) and -ru-ledger.pdf (RU, 31 pp),
  built with the Ledger journal class (pdflatex)
- LaTeX sources onix-protocol-paper.tex / -ru.tex and ledger.cls (patched for
  TeX Live 2026: caption compatibility, theorem env, babel main=english+russian,
  cleveref/amsmath order, pretitle, keywords \item scoped to its env)
- both carry the F1 early-exit section, Theorem 2 and Corollary 3, matching the
  markdown/PDF pandoc builds
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.

2 participants