Skip to content

Python client lockstep: per-topology difficulty + mineable-topology whitelist#17

Merged
rcarback merged 5 commits into
v0.2from
feat/qpow-topology-binding
Jun 24, 2026
Merged

Python client lockstep: per-topology difficulty + mineable-topology whitelist#17
rcarback merged 5 commits into
v0.2from
feat/qpow-topology-binding

Conversation

@rcarback

@rcarback rcarback commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Draft — do not merge until the runtime change is deployed. Depends on quip-protocol-rs MR !42 (per-topology difficulty + mineable-topology whitelist). The new runtime API/encoding this targets does not exist on chain until that ships.

Summary

Keeps the Python client in lockstep with the Substrate change that binds PoW difficulty per topology and gates mining on a root-controlled whitelist. Also retires the per-solution participation System.remark in favor of the pallet's native miner registry.

Changes — per-topology difficulty + whitelist

  • substrate/client.py
    • query_difficulty(topology_hash=None) now reads the per-topology QuantumPow.Difficulties map (resolves DefaultTopology when None; absent entry returns None).
    • New query_difficulty_for(topology_hash) (runtime API QuantumPowApi_difficulty_for, decoding Option<DifficultyConfig>).
    • New query_mineable_topologies() (runtime API QuantumPowApi_mineable_topologies, decoding Vec<H256>).
    • New add_mineable_topology / remove_mineable_topology submit helpers.
  • substrate/scale_codec.py: _decode_h256_vec (compact length + N×32 bytes).
  • substrate/miner_bootstrap.py: _maybe_seed_chain registers the topology before seeding difficulty and passes topology_hash to set_difficulty (now a two-argument call).
  • tools/register_advantage2.py: restructured to compute the target hash, register, whitelist (add_mineable_topology), set_difficulty(topology_hash, …), then set_default_topology — the order the new runtime requires (set_difficulty needs the topology registered; set_default_topology rejects non-whitelisted targets). This file was previously an uncommitted local tool and is now tracked.
  • tools/download_and_validate_wins.py: no change needed — already re-validates against the per-topology snapshot difficulty.

Changes — drop participation System.remark for pallet-native tracking

The per-solution participation remark was a stopgap from before the pallet had a miner registry: a free-form System.remark JSON blob per solution to record that a node was mining. The pallet now tracks this natively — register_miner reserves a deposit and records identity, and submit_proof increments proofs_submitted/proofs_won in the Miners map — so the remark is redundant transport for data the chain already owns. register_miner is already submitted at bootstrap (_ensure_registered).

Removed the whole producer→consumer chain:

  • shared/base_miner.py: participating_cb param + dispatch-gate emit + _participation_extra.
  • QPU/dwave_miner.py: _participation_extra override (budget_seconds).
  • shared/miner_worker.py: _emit_participating and its wiring.
  • substrate/miner_controller.py: _mark_participating, _submit_participation_remark, the op=="participating" drain branch, the participation dedup state, the retry constants, and the now-unused submit_remark import.
  • Tests covering the removed path in test_base_miner_pump.py, test_dwave_stream_stop.py, test_substrate_miner_controller.py.

The descriptor/identify hardware-specs remarks keep using System.remarkregister_miner takes no payload, so there is no pallet equivalent for broadcasting GPU/CPU specs.

Testing

  • ruff check / ruff format --check clean on touched files.
  • Unit and mocked tests pass (scale-codec helpers, pool client, mining-snapshot decode, miner bootstrap). New _decode_h256_vec tests build real SCALE wire bytes; new runtime-API helpers covered via monkeypatched _state_call.
  • Participation removal: 80 passed across the three affected test files; no new failures.
  • Live-chain integration tests are skipif-gated on a running node and require the deployed runtime to exercise end to end.

Live test gating

test_controller_submits_proof_end_to_end and test_controller_long_haul_multi_block drive _maybe_seed_chain, which reads/writes difficulty through the per-topology QuantumPow.Difficulties map. They now probe the connected chain's metadata (_chain_has_per_topology_difficulty) and skip cleanly against a pre-MR!42 runtime instead of hard-failing — mirroring the existing _chain_requires_hybrid_signer gate. They exercise end to end once the new runtime is deployed to the test chain.

Follow-ons (non-blocking)

  • The pool layer (pool.py / pool_client.py) forwards query_mineable_topologies but does not thread an at block parameter (matches the existing query_current_difficulty pattern); admin extrinsics are not surfaced on PoolClient (they use the direct/sudo client).

rcarback added 5 commits June 24, 2026 09:14
…e helpers

- scale_codec: add _decode_h256_vec (compact-u32 length + N×32-byte hashes)
- client: query_difficulty now takes optional topology_hash; resolves
  DefaultTopology when None and queries QuantumPow.Difficulties map
- client: add query_mineable_topologies (QuantumPowApi_mineable_topologies)
- client: add query_difficulty_for (QuantumPowApi_difficulty_for, Option-decode)
- client: add add_mineable_topology / remove_mineable_topology extrinsic helpers
- pool_client / pool: wire up new read ops as idempotent ops
- tests: update query_difficulty callers for new signature; add unit tests for
  _decode_h256_vec and monkeypatched query_mineable_topologies / query_difficulty_for
…egister_advantage2 --mineable

miner_bootstrap._maybe_seed_chain: register topology BEFORE set_difficulty
(runtime now ensure!s topology is in RegisteredTopologies); pass
topology_hash in set_difficulty params; derive seed_topology_hash from
the snapshot returned after registration (or from the existing snapshot).
query_difficulty is now called with the resolved topology hash.

register_advantage2.main: restructure to the required order —
  1. compute target_hash
  2. register_topology (idempotent)
  3. add_mineable_topology if not already whitelisted (required; runtime
     rejects set_default_topology with TopologyNotMineable otherwise)
  4. set_difficulty with {topology_hash, difficulty} (per-topology)
  5. set_default_topology (topology registered + whitelisted)

download_and_validate_wins: no change needed — already per-topology via
_topology_for(topology_hash) and snapshot.difficulty throughout.
The per-solution participation remark was a stopgap from before the
pallet had a miner registry: a free-form System.remark JSON blob per
solution to record that a node was mining. The pallet now tracks this
natively — register_miner reserves a deposit and records identity, and
submit_proof increments proofs_submitted/proofs_won in the Miners map —
so the remark is redundant transport for data the chain already owns.

Remove the whole producer->consumer chain:
- base_miner: participating_cb param + gate emit + _participation_extra
- dwave_miner: _participation_extra override (budget_seconds)
- miner_worker: _emit_participating and its wiring
- miner_controller: _mark_participating, _submit_participation_remark,
  the op=="participating" drain branch, dedup state, retry constants,
  and the now-unused submit_remark import

The descriptor/identify hardware-specs remarks keep using System.remark
(register_miner takes no payload, so there is no pallet equivalent).
test_controller_submits_proof_end_to_end and
test_controller_long_haul_multi_block drive _maybe_seed_chain, which
reads/writes difficulty through the per-topology QuantumPow.Difficulties
map (quip-protocol-rs MR !42). Against an older runtime that path raised
StorageFunctionNotFound deep in bootstrap, hard-failing instead of
skipping. Add a metadata probe (_chain_has_per_topology_difficulty) and
gate both tests on it, mirroring the existing _chain_requires_hybrid_signer
pattern, so they skip cleanly until the runtime is deployed.
test_mining_snapshot_either_returns_or_none and
test_query_difficulty_either_returns_or_none read difficulty/snapshot
through the per-topology QuantumPow.Difficulties map (rc4 / MR !42).
Against a pre-MR!42 runtime they raised StorageFunctionNotFound instead
of skipping. Add the same _chain_has_per_topology_difficulty metadata
probe used by the controller tests and gate both on it.
@rcarback rcarback marked this pull request as ready for review June 24, 2026 17:07
@rcarback rcarback merged commit 46852c9 into v0.2 Jun 24, 2026
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