Skip to content

HF14: Prediction Markets#124

Open
On1x wants to merge 38 commits into
masterfrom
pm
Open

HF14: Prediction Markets#124
On1x wants to merge 38 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.
@On1x

On1x commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Follow-up (ff25633): the market GC now reclaims every terminal market 5 days after it dies, not just resolved+paid ones — covers oracle-rejected, accept-window-expired, missed-resolution, dispute-auto-close and no-contest, in addition to resolved/settled. Added a finalized_time field on pm_market_object set at each terminal transition, and a by_finalized index so process_pm_markets() sweeps dead markets in time order (skipping the finalized_time==0 live bucket) and GCs each cluster PM_CLOSED_MARKET_RETENTION_SEC (5d) later. Deterministic (fixed constant, chain time) → snapshots stay identical network-wide. Snapshot import reads finalized_time when present. Still not compiled here — needs CI + a consensus_sim case.

Add consensus_sim scenarios asserting that every way a market can die gets its
whole object cluster reclaimed from state after the retention window:
  - gc_resolved_market_after_retention        (resolved + auto-paid)
  - gc_oracle_reject_after_retention          (oracle rejects the terms)
  - gc_accept_window_expired_after_retention  (oracle never accepts, window expires)
  - gc_oracle_missed_after_retention          (oracle misses the resolution deadline)
  - gc_dispute_auto_close_after_retention     (dispute filed, nobody votes, auto-close)

Each drives the market to its terminal state, asserts finalized_time is stamped
and the cluster is still present, then advances past the retention and asserts
the market plus its outcomes/bets/liquidity/commits/dispute-votes/leverage/
dispute/lazy-allocation rows are all gone (market_cluster_absent helper).

To make the 5-day retention testable, expose it as a median-voted chain
property pm_closed_market_retention_sec (default 432000 = 5 days) instead of a
hardcoded constant. It stays identical on every node at any block (median), so
GC remains deterministic and snapshot-safe, while tests can shorten it to 30s.
The GC cron now reads mp.pm_closed_market_retention_sec.
@On1x

On1x commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Tests (6ea7c9e): added consensus_sim scenarios for all five terminal-market GC paths in test_pm_lifecycle.cppgc_resolved_market_after_retention, gc_oracle_reject_after_retention, gc_accept_window_expired_after_retention, gc_oracle_missed_after_retention, gc_dispute_auto_close_after_retention. Each drives the market to its terminal state, asserts finalized_time is stamped and the cluster is still present, then advances past the retention and asserts the market + its whole cluster (outcomes/bets/liquidity/commits/dispute-votes/leverage/dispute/lazy-allocation) are gone (market_cluster_absent). To make the 5-day retention testable it is now a median-voted chain property pm_closed_market_retention_sec (default 432000; identical on every node → GC stays deterministic/snapshot-safe; tests shorten it to 30s). JS (#8) and PHP (#6) serializers updated in lock-step (46 PM fields). Still not built here — CI/build to confirm.

On1x added 10 commits July 6, 2026 12:24
…tor forks

On a testnet fork where one operator controls all validators, the
minority-fork detector loops forever: healthy participation (>=33%)
auto-clears the enable-stale-production override every tick, then
"last 21 blocks all ours" triggers a reset-to-LIB resync. Add an
explicit disable-minority-fork-detection flag that bypasses both the
standard and DLT detection blocks and is never auto-cleared.
…e-operator forks

- Introduce `disable-minority-fork-detection` config to fully skip minority fork detection
- Document usage warnings: only for testnets or single-operator forks, never enable in public networks
- Clarify difference from `enable-stale-production` which auto-clears at high participation
- Update validator plugin docs and configuration references to include new flag
- Explain behavior in validator-node docs and warnings for detection loop avoidance
- Note watchdog and fork detection interactions with new flag in documentation tables
The prediction_market_api plugin extracted only category/subcategory/tags/
banned_jurisdictions from a market's metadata JSON and discarded title, image
and the source condition_id, so thin clients had no human-readable question or
icon to render (markets showed as 'Market #N'). Add title, image and
condition_id to pm_market_meta_object + parse_market_metadata + FC_REFLECT and
copy them in ingest_market_meta. Non-consensus prunable index — no hardfork;
a replay re-extracts these for existing markets from the block log. condition_id
also enables reliable client back-linking of a mirrored market to its source.

Tests extended (meta_parse_test).
The consensus pm_market_object deliberately does NOT carry the free-form
metadata; the plugin parses it off-chain into pm_market_meta_index. So clients
that read market.metadata (title/image/category) got nothing — cards and the
market detail showed 'Market #N' with no thumbnail.

Add market_card(db, market): serialize the market and inject a reconstructed
'metadata' object (title/image/category/subcategory/tags[]/banned_jurisdictions[]/
condition_id) plus flat title/image/category fields. Route the market-returning
read APIs through it: get_market, list_markets, list_markets_by_oracle,
list_markets_by_creator (return vector<fc::variant>), and get_market_full (overlay
the enriched card onto the .market field). Existing clients that parse
market.metadata now work unchanged; no consensus/state change, no replay.

remote_node_api already types these as fc::variant, so the wallet/CLI is unaffected.
Read-only query for the markets that need an oracle's result now: active
(status 1) markets whose betting window has closed (betting_expiration <=
head_block_time) and are therefore not yet resolved. "Awaiting" is not a
distinct status — a market stays active from open through betting-close until
it is resolved — so it can't be filtered by status alone. Walk the
by_betting_expiration index (keyed status, betting_expiration, id) over the
bounded prefix of active markets past their betting deadline and keep the
given oracle's rows. This lets an Oracle Console list pending-resolution
markets without scanning the oracle's full (mostly resolved) history client-side.

Plugin-only read method — no consensus change.
…et meta

The parser already ships each market's short rules text under metadata.description
(Polymarket description / Kalshi rules_primary), but meta_parse dropped it. Add
description to the meta whitelist: parse it, store it in pm_market_meta_object, and
return it in market_card's metadata. The on-chain url still points to the full legal
terms at the source; description is the short "how the oracle resolves" text for
clients. Display/indexing only — no consensus change, no hardfork.
list_markets gains an optional order arg ("oldest" default = legacy, "newest"
= id desc via reverse traversal of the by_status equal-range). Discovery feeds
need newest-first without a full scan.

Meta backfill: after --replay-from-snapshot, off-chain market metadata is rebuilt
only for markets whose create op fell in the reindex window. The DLT rolling block
log can reach further back than the snapshot, so those older create ops are still
on disk. A one-shot pass (run from on_block, budgeted 500 blocks/apply) scans the
DLT log and re-ingests meta for any market still missing it. Op->market mapping is
positional per block (meta ingest is all-or-nothing per block, so the k-th create
op created the k-th market of that block); create_meta_for() is idempotent.
Runs post-replay (reindex_from_dlt does not flush applied_block), no-op when nothing
is missing (normal restart) or when there is no DLT log.
…ional)

The first backfill mapped create ops to markets positionally by block timestamp,
assuming market.created_time == the DLT block's own timestamp. created_time is set
from head_block_time() which lags by a block, so the timestamp bucket was off and
titles landed on the wrong markets (observed shift on testnet).

Replace with a strong identity key (creator + url + betting/result expiration +
outcome count) — all consensus fields the evaluator copies verbatim from the op.
A key mismatch now simply leaves a market's meta empty; it can never mis-assign a
title to the wrong market. Duplicate keys resolve in id order via a small list.

Recovery: replay-from-snapshot starts with an empty meta index, so rebuilding the
node on this image re-ingests all meta correctly (the previous wrong meta is gone).
…ves)

A market created with betting_expiration == 0 keeps betting open until the
oracle resolves it; result_expiration (<= now + pm_max_market_duration, i.e.
<= 1 year) becomes a pure emergency backstop. If the oracle never resolves by
then, the existing missed-resolution path in process_pm_markets refunds every
bet and slashes the oracle insurance — so the "oracle abandoned the market"
case has a trustless recourse without a dispute.

- create: betting_expiration==0 branch requires allow_early_resolution and a
  result_expiration within (now, now + pm_max_market_duration]. Non-open-ended
  markets keep the existing checks.
- betting / liquidity gates (place, commit, reveal, add, withdraw): treat 0 as
  "open while the market is active".
- leverage: available on open-ended markets too (the extra volatility is the
  bettor's own risk). The expiration-buffer check only applies when there is a
  real betting deadline; guarding it also avoids the epoch-0 underflow of
  (betting_expiration - buffer).
- prediction_market_api: mirror the leverage buffer guard; report
  auto_close_time = 0 (no force-close point) for open-ended markets.

Resolve evaluator and the missed-resolution backstop are reused unchanged.
Gated by HF14, no separate feature flag.
Adds a time-based funding cost to leverage positions so a long-held loan (now
possible on open-ended markets) pays the lazy pool for the capital it ties up.

- New median-voted chain property pm_leverage_funding_rate_ppm_per_day (uint32,
  ppm of the loan per 24h; default 50 = 0.005%/day; 0 disables). Validated <= 100%/day.
- pm_leverage_position_object gains funding_paid (cumulative) and funding_due_time
  (next 24h boundary), plus a by_lev_funding_due index (status, funding_due_time, id).
- accrue_leverage_funding() charges whole 24h periods (one-shot catch-up) from the
  bettor's equity into funding_paid, which raises the effective obligation
  (liquidation_threshold + funding_paid) and thus pulls up the liquidation point.
  Funding flows to LP yield through the existing pool_profit path.
- process_pm_markets() gets a funding sweep: for each due active position, accrue,
  reprice at current reserves, and liquidate (reason 3) if now underwater.
- open sets funding_due_time = created_time + 24h; close/convert/liquidate accrue
  first and settle funding to the pool. cascade trigger includes funding_paid.
- prediction_market_api leverage quote exposes funding_rate_ppm_per_day;
  get_pm_chain_properties returns the new property automatically.

Snapshot uses generic FC_REFLECT import/export so the new fields are covered.
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.

1 participant