## File: README.md # Quip Network node Quip Network solochain node (`quip-network-node`). ## Chain specs Omit `--chain` to join the live public Quip Testnet (EIP-155 `20033`). `--chain=local` is a disposable Alice-and-Bob chain. It is not the public testnet. | Flag | Preset | Network | EIP-155 | |------|--------|---------|--------:| | (omit `--chain`) | `quip-testnet` | live public testnet | 20033 | | `--chain=quip-testnet` | `quip-testnet` | live public testnet | 20033 | | `--dev` or `--chain=dev` | Development | Alice-only local | 1337 | | `--chain=local` | Local Testnet | Alice and Bob local | 1337 | | `--chain=local3` | Local Testnet (3 Validators) | Alice, Bob, and Charlie local | 1337 | Aliases for the public testnet: `quip_testnet`, `testnet`. A path argument loads that JSON file and uses the chain ID in its genesis. ## Getting Started Depending on your operating system and Rust version, there might be additional packages required to compile this template. Check the [Install](https://docs.substrate.io/install/) instructions for your platform for the most common dependencies. Alternatively, you can use one of the [alternative installation](#alternatives-installations) options. Fetch solochain template code: ```sh git clone https://github.com/paritytech/polkadot-sdk-solochain-template.git solochain-template cd solochain-template ``` ### Build 🔨 Use the following command to build the node without launching it: ```sh cargo build --release ``` ### Join the live public testnet Omit `--chain` to select `quip-testnet` (EIP-155 `20033`). ```sh # Live public testnet (EIP-155 20033). Default if you omit --chain. ./target/release/quip-network-node ``` ### Embedded Docs After you build the project, you can use the following command to explore its parameters and subcommands: ```sh ./target/release/solochain-template-node -h ``` You can generate and view the [Rust Docs](https://doc.rust-lang.org/cargo/commands/cargo-doc.html) for this template with this command: ```sh cargo +nightly doc --open ``` ### Single-Node Development Chain The following command starts a single-node development chain that does not persist state. `--dev` is the Alice-only local preset (EIP-155 `1337`), not the public testnet. ```sh # Alice-only local chain (EIP-155 1337) ./target/release/solochain-template-node --dev ``` To purge the development chain's state, run the following command: ```sh # Alice-only local chain (EIP-155 1337) ./target/release/solochain-template-node purge-chain --dev ``` To start the development chain with detailed logging, run the following command: ```sh # Alice-only local chain (EIP-155 1337) RUST_BACKTRACE=1 ./target/release/solochain-template-node -ldebug --dev ``` Development chains (`--dev` / `--chain=dev`): - Maintain state in a `tmp` folder while the node is running. - Use the **Alice** account as the sole validator authority. - Use the **Alice** account as the default `sudo` account. - Are preconfigured with a genesis state (`/node/src/chain_spec.rs`) that includes several pre-funded development accounts. - Use EIP-155 chain ID `1337`. To persist chain state between runs, specify a base path by running a command similar to the following: ```sh // Create a folder to use as the db base path $ mkdir my-chain-state // Alice-only local chain (EIP-155 1337) $ ./target/release/solochain-template-node --dev --base-path ./my-chain-state/ // Check the folder structure created inside the base path after running the chain $ ls ./my-chain-state chains $ ls ./my-chain-state/chains/ dev $ ls ./my-chain-state/chains/dev db keystore network ``` ### Connect with Polkadot-JS Apps Front-End After you start the node template locally, you can interact with it using the hosted version of the [Polkadot/Substrate Portal](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) front-end by connecting to the local node endpoint. A hosted version is also available on [IPFS](https://dotapps.io/). You can also find the source code and instructions for hosting your own instance in the [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. Quip uses hybrid BABE and GRANDPA consensus keys. Polkadot.js Apps does not require custom types for Quip anymore; for usage notes, see [docs/polkadotjs/README.md](/Users/romanuseinov/projects/quip/quip-protocol-rs/docs/polkadotjs/README.md). ### Multi-Node Local Testnet A scripted three-validator local network is available two ways. Both use `--chain=local3` (EIP-155 `1337`). This is not the public testnet. - **Native build:** `scripts/start-local3.sh` builds the debug binary and starts three validators (Alice/Bob/Charlie) against the embedded `local3` chain spec. - **Docker:** `docker compose up --build` starts the same three-validator topology in containers. See the [Docker](#docker) section below. Both paths use the same hardcoded libp2p node-keys and bootnode peer ID, so they're interchangeable for development. For background on multi-node consensus, see [Simulate a network](https://docs.substrate.io/tutorials/build-a-blockchain/simulate-network/). ## Template Structure A Substrate project such as this consists of a number of components that are spread across a few directories. ### Node A blockchain node is an application that allows users to participate in a blockchain network. Substrate-based blockchain nodes expose a number of capabilities: - Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the nodes in the network to communicate with one another. - Consensus: Blockchains must have a way to come to [consensus](https://docs.substrate.io/fundamentals/consensus/) on the state of the network. Substrate makes it possible to supply custom consensus engines and also ships with several consensus mechanisms that have been built on top of [Web3 Foundation research](https://research.web3.foundation/Polkadot/protocols/NPoS). - RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. There are several files in the `node` directory. Take special note of the following: - [`chain_spec.rs`](./node/src/chain_spec.rs): A [chain specification](https://docs.substrate.io/build/chain-spec/) is a source code file that defines a Substrate chain's initial (genesis) state. Chain specifications are useful for development and testing, and critical when architecting the launch of a production chain. Take note of the `development_config` and `testnet_genesis` functions. These functions are used to define the genesis state for the local development chain configuration. These functions identify some [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) and use them to configure the blockchain's initial state. - [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of the libraries that this file imports and the names of the functions it invokes. In particular, there are references to consensus-related topics, such as the [block finalization and forks](https://docs.substrate.io/fundamentals/consensus/#finalization-and-forks) and other [consensus mechanisms](https://docs.substrate.io/fundamentals/consensus/#default-consensus-models) such as BABE for block authoring and GRANDPA for finality. ### Runtime In Substrate, the terms "runtime" and "state transition function" are analogous. Both terms refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. The Substrate project in this repository uses [FRAME](https://docs.substrate.io/learn/runtime-development/#frame) to construct a blockchain runtime. FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". At the heart of FRAME is a helpful [macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address [a variety of needs](https://substrate.io/ecosystem/projects/). Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: - This file configures several pallets to include in the runtime. Each pallet configuration is defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. - The pallets are composed into a single runtime by way of the [#[runtime]](https://paritytech.github.io/polkadot-sdk/master/frame_support/attr.runtime.html) macro, which is part of the [core FRAME pallet library](https://docs.substrate.io/reference/frame-pallets/#system-pallets). ### Pallets The runtime in this project is constructed using many FRAME pallets that ship with [the Substrate repository](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame) and a template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory. A FRAME pallet is comprised of a number of blockchain primitives, including: - Storage: FRAME defines a rich set of powerful [storage abstractions](https://docs.substrate.io/build/runtime-storage/) that makes it easy to use Substrate's efficient key-value database to manage the evolving state of a blockchain. - Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) from outside of the runtime in order to update its state. - Events: Substrate uses [events](https://docs.substrate.io/build/events-and-errors/) to notify users of significant state changes. - Errors: When a dispatchable fails, it returns an error. Each pallet has its own `Config` trait which serves as a configuration interface to generically define the types and parameters it depends on. ## Alternatives Installations Instead of installing dependencies and building this source directly, consider the following alternatives. ### Nix Install [nix](https://nixos.org/) and [nix-direnv](https://github.com/nix-community/nix-direnv) for a fully plug-and-play experience for setting up the development environment. To get all the correct dependencies, activate direnv `direnv allow`. ### Docker A multi-stage `Dockerfile` builds the `quip-network-node` binary on top of `debian:bookworm-slim` (~80 MB runtime image). The image exposes the binary directly as ENTRYPOINT, so any Substrate CLI flag works at `docker run` time. #### Build ```sh docker build -t quip-network-node:local . ``` The first build compiles the full workspace and takes a while. BuildKit cache mounts (declared in the Dockerfile) keep the cargo registry and target directory between local rebuilds. #### Pre-built images Every push to `main` and every git tag publishes an image to the project's GitLab Container Registry, so you don't have to build locally: ```sh docker pull registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:latest ``` Tag scheme: - `:latest` — tip of `main`. Floating, advances on every merge. - `:sha-` — pinned to a specific commit on `main` or to a tagged release. - `:` — pinned to a release tag (e.g. `:v0.1.0`). #### Run as a validator ```sh # Three-validator local chain (EIP-155 1337). Not the public testnet. docker run --rm -v quip-data:/data -p 9944:9944 -p 30333:30333 \ quip-network-node:local \ --chain=local3 --base-path=/data \ --validator --alice \ --unsafe-rpc-external --rpc-cors=all ``` `--unsafe-rpc-external` is required because Substrate refuses to combine `--rpc-external` with `--validator` by default (a safety guard against exposing a validator's RPC to the public internet). For local development the unsafe flag is fine; for production validators you almost certainly do not want any external RPC at all. #### Run as a full node Same command, omit `--validator` (and the `--alice/--bob/--charlie` shortcut): ```sh # Three-validator local chain (EIP-155 1337). Not the public testnet. docker run --rm -v quip-data:/data -p 9944:9944 -p 30333:30333 \ quip-network-node:local \ --chain=local3 --base-path=/data \ --bootnodes=/dns//tcp/30333/p2p/ \ --rpc-external --rpc-cors=all ``` #### Local 3-node network via docker-compose `docker-compose.yml` reproduces `scripts/start-local3.sh` in containers. Each service passes `--chain=local3` (EIP-155 `1337`). This is not the public testnet. ```sh docker compose up --build # start local3 (EIP-155 1337) docker compose down # stop, keep chain state docker compose down -v # stop and wipe state ``` Then connect Polkadot.js Apps to `ws://localhost:9944` (node1), `ws://localhost:9945` (node2), or `ws://localhost:9946` (node3). ## Public testnet `quip-testnet` is the live public testnet ("AGLS" tokens, 12 decimals, EIP-155 `20033`). Omitting `--chain` selects this preset. The canonical genesis is baked into the `v0.2.0+` binary and also published as a raw JSON file at `nodes.quip.network/chain-specs/quip-testnet.json`. ### Quickstart (Docker) ```sh # Pull the matching release image docker pull registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v0.2.0 # Live public testnet (EIP-155 20033). Omit --chain for the same preset. docker run --rm -v quip-data:/data -p 9944:9944 -p 30333:30333 \ registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v0.2.0 \ --chain=quip-testnet --base-path=/data \ --name="my-quip-node" ``` The three canonical bootnodes (`bootnode-{1,2,3}.testnet.quip.network`) are embedded in the chain spec, so peer discovery happens automatically. ### Using the hosted raw chain spec Alternatively, fetch the published JSON spec from `nodes.quip.network` and pass its path to `--chain`. That file is the live public testnet (EIP-155 `20033`). ```sh curl -fsSL https://gitlab.com/quip.network/nodes.quip.network/-/raw/main/chain-specs/quip-testnet.json \ -o quip-testnet.json # Live public testnet JSON (EIP-155 20033) docker run --rm -v "$PWD:/spec" -v quip-data:/data -p 9944:9944 -p 30333:30333 \ registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v0.2.0 \ --chain=/spec/quip-testnet.json --base-path=/data ``` ### Running a validator Operator validator slots are committed at genesis (see [`docs/genesis-quip-testnet.md`](docs/genesis-quip-testnet.md)). To rotate or add a slot, follow [`docs/testnet-keys.md`](docs/testnet-keys.md) and the `scripts/derive-operator-keys.sh` helper. --- ## File: docs/polkadotjs/README.md # Polkadot.js Apps Support Quip uses hybrid consensus keys: - BABE authority key: `sr25519 + ML-DSA-44` (`H3`) - GRANDPA authority key: `ed25519 + ML-DSA-44` (`H1`) These consensus keys are not browser-managed account keys. Polkadot.js Apps can connect to Quip without custom type overrides, but validator/session key management still goes through the node keystore rather than browser-generated consensus keys. ## Current Support Model Polkadot.js Apps works normally for: - connect to the Quip node over WebSocket - inspect storage, events, and extrinsics - call `author_rotateKeys` - submit `session.setKeys` The browser does **not** need to generate hybrid BABE or GRANDPA keys directly. Session keys should continue to be generated by the node keystore through `author_rotateKeys`, then registered on-chain with `session.setKeys`. Browser-side transaction signing now has an initial WASM/signature wrapper in this repository and is tracked in [`wasm-signing-plan.md`](./wasm-signing-plan.md). ## Regenerating the signing fixture `fixtures/hybrid-signing.json` embeds drift-prone runtime values (spec version, transaction version, extension set), so `cargo test -p quip-protocol-runtime --test signing_fixture` fails whenever those change. Regenerate it with: ```bash cargo run -p quip-protocol-runtime --example generate_polkadotjs_signing_fixture -- --write ``` Then re-run the signing-fixture tests and `npm test --prefix js/quip-signer`, which both consume the fixture. ## Metadata Compatibility Quip exposes the hybrid BABE and GRANDPA types through metadata-safe wrapper shapes, so current Polkadot.js Apps builds should not require any manual custom types just to connect and decode the runtime metadata. This matters because the hybrid BABE and GRANDPA signatures are larger than the classical Substrate defaults, and older metadata representations using a single large `[u8; N]` array can exceed client-side parser limits. If Apps has cached older metadata from before this fix, reconnecting may still show stale decode errors. In that case, hard-refresh the page or remove the saved endpoint and reconnect. ## What This Does Not Add This does not add browser-side generation or signing support for hybrid BABE or GRANDPA authority keys. For validator/session key management, the intended flow remains: 1. `author_rotateKeys` 2. `session.setKeys` That is the supported path for Quip's hybrid consensus keys in Polkadot.js Apps. --- ## File: docs/quantum-compute-mempool.md # Quantum Compute Mempool ## Canonical Plain Ising Job Spec `QuantumComputeMempool` seeds a default plain Ising job spec at genesis so SDKs can call `propose_job` without first registering a spec. Canonical tuple: ```text name = "plain-ising-v1" formulation = Ising validation_program = None transform_program = None ``` The `spec_id` is the runtime hash of the SCALE-encoded tuple: ```text (name, formulation, validation_program, transform_program) ``` Pinned `spec_id`: NOTE: This value is verified by CI (default_ising_spec_id_is_pinned and default_ising_spec_id_matches_pinned_hash); if any tuple field changes, those tests will fail. ```text 0x8f46f3a31321d1d093314fc769c42cbe7a83d71a0b69e6571a0f68e2a04067f0 ``` SDKs may hardcode this value or read the `DefaultIsingSpecId` pallet constant from metadata. Additional job specs are team-controlled: `register_job_spec` requires root origin and records an explicit builder account supplied by root. ## Coefficient and Solution Domains `QuantumComputeMempool` stores plain Ising problems. The on-chain solution domain is spin space: ```text s_i in {-1, +1} ``` The pallet does not accept binary solution values such as `0` and `1` for plain Ising jobs. If an SDK accepts a QUBO or binary-domain model from a user, the SDK should transform it off-chain before calling `propose_job`, and should map returned spin solutions back to the user's binary domain if needed: ```text x_i = (s_i + 1) / 2 s_i = 2x_i - 1 ``` Coefficients are fixed-point milli values. `h_values` and `j_values` are `i32` values where `1000` represents `1.0`. For example: ```text 5.0 -> 5000 0.25 -> 250 -1.5 -> -1500 ``` The same milli convention is used for `best_energy_milli`, `min_energy_milli`, `diversity_milli`, and `min_diversity_milli`. Energy values are signed (`i64`); diversity values are unsigned (`u32`). Keep any constant offset introduced by a QUBO-to-Ising transform in the adapter, or adjust user-facing energy thresholds before submitting the job. ## Accepted Solution Storage Accepted solver submissions are stored in `OrderSolutions`, a `DoubleMap` keyed first by `order_id` and second by solver account: ```rust pub type OrderSolutions = StorageDoubleMap< _, Blake2_128Concat, u64, Blake2_128Concat, T::AccountId, JobSolutionOf, >; ``` Each stored `JobSolution` contains: ```text solver solver_type solutions best_energy_milli diversity_milli num_valid submitted_at ``` Because this storage item is exposed through runtime metadata, SDKs can query it through the standard Substrate storage RPC. No custom RPC is needed unless a client cannot perform or decode metadata-backed `DoubleMap` prefix queries. ### Query with substrate-interface Install the Python client: ```bash pip install substrate-interface ``` Connect to a node over websocket: ```python from substrateinterface import SubstrateInterface substrate = SubstrateInterface(url="ws://127.0.0.1:9944") ``` Read one solver's accepted submission for an order: ```python order_id = 0 solver_ss58 = "5..." solution = substrate.query( module="QuantumComputeMempool", storage_function="OrderSolutions", params=[order_id, solver_ss58], ) if solution.value is not None: print(solution.value["solver"]) print(solution.value["solutions"]) print(solution.value["best_energy_milli"]) print(solution.value["diversity_milli"]) ``` Read all accepted submissions for one order by querying the `DoubleMap` with only the first key: ```python order_id = 0 rows = substrate.query_map( module="QuantumComputeMempool", storage_function="OrderSolutions", params=[order_id], page_size=100, ) for solver_key, solution in rows: print("solver key:", solver_key.value) print("stored solver:", solution.value["solver"]) print("solutions:", solution.value["solutions"]) print("best energy:", solution.value["best_energy_milli"]) print("diversity:", solution.value["diversity_milli"]) ``` Use `block_hash=` on `query` or `query_map` when an adapter needs a historical or finalized view instead of the current best block. ## Job Lifecycle Events `QuantumComputeMempool` emits lifecycle events that SDKs can monitor through `System.Events`. The most useful events for result retrieval are: ```text JobProposed SolutionAccepted FirstSolutionReceived BlockWaitStarted FrontRunnerChanged OrderExpired OrderClosed ResultReady ``` `SolutionAccepted` is emitted when a solver submission is accepted and written to `OrderSolutions`. `ResultReady` is emitted when settlement produces a final winner payload for callback delivery modes. Settlement also emits `RewardClaimed` for each winner payout, and `ResultPurged` is emitted when a stored poll payload is deleted after its TTL — `CallbackWithPoll` consumers must fetch results before the purge. `OrderExpired` is emitted lazily when an extrinsic touches an expired open order and the pallet updates the status. It is not emitted automatically at the exact expiry block, so SDKs that need exact deadline handling should also read `JobOrders` and compute the effective expiry as: ```text min(created_at + timing.deadline_blocks, first_solution_at + timing.block_wait) ``` Before the first solution arrives, `first_solution_at` is null and the expiry is just `created_at + timing.deadline_blocks`. Note that `created_at` and `first_solution_at` are top-level `JobOrder` fields, while `deadline_blocks` and `block_wait` are nested inside the order's `timing` struct. ### Read events from a block with substrate-interface ```python EVENTS = { "JobProposed", "SolutionAccepted", "FirstSolutionReceived", "BlockWaitStarted", "FrontRunnerChanged", "OrderExpired", "OrderClosed", "ResultReady", } def mempool_events_at(block_hash=None): for event in substrate.get_events(block_hash=block_hash): if ( event.value["module_id"] == "QuantumComputeMempool" and event.value["event_id"] in EVENTS ): yield event for event in mempool_events_at(): print(event.value["event_id"], event.value["attributes"]) ``` ### Subscribe to event storage with substrate-interface For live best-block monitoring, subscribe to `System.Events` and filter the decoded event records: ```python def event_name_from_storage_record(record): event = record.get("event", {}) module = event.get("module_id") or event.get("module") name = event.get("event_id") or event.get("name") return module, name def handle_events(events_obj, update_nr, subscription_id): if update_nr == 0: return None for record in events_obj.value: module, name = event_name_from_storage_record(record) if module == "QuantumComputeMempool" and name in EVENTS: print(name, record) return None substrate.query( module="System", storage_function="Events", subscription_handler=handle_events, ) ``` For finalized-only processing, subscribe to finalized or imported block headers in the adapter process, then call `get_events(block_hash=...)` for each block hash before acting on the events. --- ## File: docs/quantum-pow-difficulty-convergence-plan.md # Quantum PoW Difficulty Convergence Plan ## Terminology A **qblock** (also `qpow_block`) is a chain block won by a quantum PoW proof — what we previously referred to as "solution #N" or "problem #N". The timing thresholds in this document are measured in elapsed *chain blocks* (6-second Substrate blocks) between consecutive qblocks: a "fast qblock" is one mined fewer than 60 chain blocks after the previous qblock. ## Problem Current PoW difficulty adjustment can converge into energy ranges that are effectively unsolvable for the current design. A fast qblock can push `max_energy_milli` too hard, and recovery then depends on decay sweeps that can take hours before the threshold becomes mineable again. The main causes are: - The runtime energy curve uses `c = 0.700 / 0.750 / 0.800`, making the hard end too negative. - The hardening cutoff is effectively 100 chain blocks, or roughly 600 seconds at six-second blocks. - The prior miner-type/QPU dominance easing behavior is no longer represented. We cannot reliably use miner type today, but we can detect repeated qblock wins by the same account. ## Goals - Keep difficulty in a theoretically solvable range. - Restore a roughly 10-minute convergence target after qblocks. - Reduce difficulty when the same miner account dominates consecutive qblocks. - Avoid changing extrinsic arguments or signed transaction encoding. - Keep decay interval and proof hardening cutoff separate. ## Proposed Changes ### 1. Recalibrate the Energy Curve Change runtime PoW curve constants in `runtime/src/configs/mod.rs`: ```rust pub const QuantumPowCurveCEasyMilli: u32 = 700; pub const QuantumPowCurveCKneeMilli: u32 = 725; pub const QuantumPowCurveCHardMilli: u32 = 750; ``` This replaces the current `0.700 / 0.750 / 0.800` curve with `0.700 / 0.725 / 0.750`. Expected effect: - The knee moves back into the intended range. - The hard edge is still difficult, but no longer pushed into the known impossible range. - Future tuning can move the middle value slightly upward, for example `0.728` or `0.730`, if observed chain data supports it. Update mock/test curve helpers to use the configured `700 / 725 / 750` constants and the spec-aware `EnergyCurve::new(..., CurveC, allowed_h, allowed_j)` constructor. ### 2. Separate Hardening Cutoff from Decay Interval Keep `QuantumPowEpochLength = 100` as the decay interval. In `pallets/quantum-pow/src/difficulty.rs`, keep the v0.1 block-native thresholds: ```rust const FAST_PROOF_BLOCKS: u64 = 60; const TARGET_PROOF_BLOCKS: u64 = 100; const SLOW_PROOF_BLOCKS: u64 = 200; ``` Direction follows the v0.1 `compute_next_block_requirements` policy: - A qblock mined before 60 chain blocks (360s) always hardens, even for a dominant winner. - A qblock at or after 60 chain blocks hardens gently (graduated 35%→5% band) unless the winner is dominant (see Section 3), in which case it eases. `TARGET_PROOF_BLOCKS = 100` no longer decides direction — only the rate bands: hardening interpolates 35%±30% → 5%±4% across 60–100 chain blocks, easing interpolates 2.5%±2% → 15%±14% across 100–200 chain blocks, exactly as v0.1 did across 360–600s and 600–1200s. The decay interval (`EpochLength = 100`) remains a separate concept. The 100-block value of `TARGET_PROOF_BLOCKS` is deliberately co-located with `QuantumPowEpochLength = 100`: the first decay step, the hardening band's gentle plateau, and the easing rate ramp all begin at the same 100-chain-block (600s) boundary. This yields three clean qblock cases: 1. **Fast qblock (< 60 chain blocks):** always hardens at 35%±30%, dominant or not. 2. **Regular qblock (60–99 chain blocks):** sub-epoch, so no decay has occurred; the adjustment starts from the stored difficulty. Streaks decide direction — a dominant winner eases, anyone else hardens on the graduated 35%→5% band. 3. **Slow qblock (≥ 100 chain blocks):** decay has already eased the stored difficulty by `elapsed / EpochLength` steps; the adjustment starts from that decay-eased base. Streaks decide direction, and hardening sits on the gentle 5%±4% plateau — so a long round can end easier overall even though the qblock itself hardened. `TARGET_PROOF_BLOCKS` and `QuantumPowEpochLength` stay separate constants (rate-band anchor vs decay cadence). Retuning the epoch length does not move the rate bands — change both together or the three-case model above stops holding. ### 3. Add Dominant-Winner Easing Add storage to track consecutive qblocks won by the same miner account. A simple shape is enough: ```rust pub type WinnerStreak = StorageValue< _, types::WinnerStreak, OptionQuery, >; ``` Add a type in `pallets/quantum-pow/src/types.rs`: ```rust pub struct WinnerStreak { pub miner: AccountId, pub count: u32, } ``` Add a runtime config constant: ```rust type ConsecutiveWinnerEasingThreshold: Get; ``` Recommended runtime value: ```rust pub const QuantumPowConsecutiveWinnerEasingThreshold: u32 = 3; ``` Policy: - If the current qblock winner is the same account as the stored streak miner, increment the streak count. - If the current winner differs, reset the streak to `{ miner, count: 1 }`. - A winner with streak count at or above the threshold is *dominant*: its qblocks at or past 60 chain blocks ease difficulty instead of hardening. - Fast qblocks (under 60 chain blocks) always harden, dominant or not — matching v0.1, where the under-360s harden rule took precedence over repeat-winner easing. - Non-dominant slow qblocks harden gently. v0.1 eased any repeat winner (a streak of 2, keyed on miner type); we instead require the configured threshold (3) by account, so a winner must demonstrate sustained dominance before difficulty pressure reverses. - A threshold of `0` disables dominant-winner easing. This restores the spirit of miner-type/QPU awareness without adding node descriptors or changing miner registration. ### 4. Clamp Existing Impossible Difficulty on Runtime Upgrade `pallet_quantum_pow` does not currently have a storage version. Add one and wire an `on_runtime_upgrade`. Migration behavior: - Read `DefaultTopology`. - Read the matching registered topology. - Build the new curve from the recalibrated constants. - If `Difficulty.max_energy_milli < curve.min_milli`, clamp it to `curve.knee_milli`. - Preserve `min_solutions` and `min_diversity_milli`. - If no default topology is registered, no-op. Rationale: - Changing constants fixes future adjustment, but existing chain state may already hold an impossible threshold. - Clamping only out-of-range values avoids disturbing healthy deployments. ### 5. Runtime Versioning Bump `spec_version` in `runtime/src/lib.rs`. Do not bump `transaction_version` unless extrinsic arguments change. This plan does not require any extrinsic encoding changes. ## Tests Add or update tests for: - New curve constants: `700 / 725 / 750`. - Curve ordering remains `min_milli < knee_milli < max_milli`. - Fast qblock before 60 chain blocks hardens, including for a dominant winner. - Slow qblock at or after 60 chain blocks hardens gently for a non-dominant winner (including a different winner — the restored v0.1 rule). - Consecutive same-miner slow qblocks at the threshold ease instead of harden. - Winner streak resets when a different miner wins a qblock. - Migration clamps an out-of-range `Difficulty.max_energy_milli` to the new knee. - Migration leaves in-range difficulty unchanged. - Existing decay and mining snapshot tests use the recalibrated curve. ## Non-Goals - Do not add miner type or node descriptor metadata in this changeset. - Do not change proof submission extrinsic arguments. - Do not change `QuantumPowEpochLength` unless separate chain data shows decay cadence itself needs tuning. - Do not change `min_solutions` or `min_diversity_milli` as part of automatic adjustment. --- ## File: docs/release.md # Release Checklist ## Pre-tag verification - [ ] All target-version commits merged to `main` via MR - [ ] `cargo check --workspace --all-targets` clean - [ ] `cargo clippy --workspace --all-targets -- -D warnings` clean - [ ] `cargo test --workspace` passes - [ ] Node image builds locally: `docker build -t quip-network-node:rc .` - [ ] Sidecar Dockerfile passes its static build check: `docker build --check -f docker/revive-eth-rpc.Dockerfile .` - [ ] `./target/release/quip-network-node --version` reports the target version - [ ] `./target/release/quip-network-node export-chain-spec --chain quip-testnet --raw > /tmp/quip-testnet.raw.json` succeeds - [ ] Companion `nodes.quip.network` MR with the matching `chain-specs/quip-testnet.json` (sha256 from the above raw export) is merged ## Tag ```bash git checkout # main or the active series branch (e.g. v0.2) — git pull # this choice decides the floating tag (see below) git tag -a v.. -m "v..: " git push origin v.. ``` The GitLab CI pipeline at `.gitlab-ci.yml` picks up the tag via the `$CI_COMMIT_TAG` rule and publishes `registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v..` and `registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-evm-sidecar:v..`. ### Version-tag format (shared standard) Pre-release tags use **SemVer hyphenated** pre-releases — `v..-rcN` (e.g. `v0.2.1-rc18`), **never** the PEP 440 no-hyphen form `v0.2.1rc18`. This is the cross-repo standard so `quip-node-manager` (and any SemVer consumer) can order release candidates correctly; see `quip-protocol/docs/VERSIONING.md` for the full rationale. Both container images publish on release tags **only** — branch pushes never build or push images. Their floating tags follow the branch the tag was cut from, resolved by the `resolve-floating-tag` CI job via commit ancestry (tag pipelines carry no branch variable): a tag on `v0.2` publishes `:` + `:v0.2`, a tag on `main` publishes `:` + `:latest`, both plus `:sha-`. A tag reachable from neither branch gets only `:` + `:sha-`. ## Post-tag verification - [ ] CI pipeline on the tag completes green (`glab ci status --live`) - [ ] Both images are present: ```bash docker pull registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v.. docker pull registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-evm-sidecar:v.. ``` - [ ] Sidecar image starts and reports its CLI help: ```bash docker run --rm \ registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-evm-sidecar:v.. \ --help ``` - [ ] Smoke test against the published spec: ```bash curl -fsSL https://gitlab.com/quip.network/nodes.quip.network/-/raw/main/chain-specs/quip-testnet.json \ -o /tmp/quip-testnet.json docker run --rm -v /tmp:/spec \ registry.gitlab.com/quip.network/quip-protocol-rs/quip-network-node:v.. \ --chain=/spec/quip-testnet.json --tmp --name v-smoke --no-mdns ``` Expect peer discovery against at least one of the three canonical bootnodes within 60 seconds. ## What v0.2.0 ships - First semver tag for the validator image; previously only `:latest` and `:sha-` were published. - Built-in `quip-testnet` chain spec preset with three operator-controlled bootnodes and a `ChainType::Live` genesis. - Helper script (`scripts/derive-operator-keys.sh`) and example (`crates/transaction-crypto/examples/derive_genesis_keys.rs`) for reproducing operator key generation end-to-end. - macOS-only `.cargo/config.toml` rpath fix so `cargo build` works on a fresh Xcode install without `LIBCLANG_PATH` exports. Runtime `spec_version` remains at `101`; v0.2.0 is packaging plus the named testnet identity, not a runtime upgrade. --- ## File: docs/rust-setup.md # Installation This guide is for reference only, please check the latest information on getting started with Substrate [here](https://docs.substrate.io/main-docs/install/). This page will guide you through the **2 steps** needed to prepare a computer for **Substrate** development. Since Substrate is built with [the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is prepare the computer for Rust development - these steps will vary based on the computer's operating system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the commands for Rust's toolchains will be the same for all supported, Unix-based operating systems. ## Build dependencies Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples in the [Substrate Docs](https://docs.substrate.io) use Unix-style terminals to demonstrate how to interact with Substrate from the command line. ### Ubuntu/Debian Use a terminal shell to execute the following commands: ```bash sudo apt update # May prompt for location information sudo apt install -y git clang curl libssl-dev llvm libudev-dev ``` ### Arch Linux Run these commands from a terminal: ```bash pacman -Syu --needed --noconfirm curl git clang ``` ### Fedora Run these commands from a terminal: ```bash sudo dnf update sudo dnf install clang curl git openssl-devel ``` ### OpenSUSE Run these commands from a terminal: ```bash sudo zypper install clang curl git openssl-devel llvm-devel libudev-devel ``` ### macOS > **Apple M1 ARM** If you have an Apple M1 ARM system on a chip, make sure that you have Apple Rosetta 2 installed > through `softwareupdate --install-rosetta`. This is only needed to run the `protoc` tool during the build. The build > itself and the target binaries would remain native. Open the Terminal application and execute the following commands: ```bash # Install Homebrew if necessary https://brew.sh/ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # Make sure Homebrew is up-to-date, install openssl brew update brew install openssl ``` ### Windows **_PLEASE NOTE:_** Native Windows development of Substrate is _not_ very well supported! It is _highly_ recommended to use [Windows Subsystem Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) (WSL) and follow the instructions for [Ubuntu/Debian](#ubuntudebian). Please refer to the separate [guide for native Windows development](https://docs.substrate.io/main-docs/install/windows/). ## Rust developer environment This guide uses installer and the `rustup` tool to manage the Rust toolchain. First install and configure `rustup`: ```bash # Install curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Configure source ~/.cargo/env ``` Configure the Rust toolchain to default to the latest stable version, add nightly and the nightly wasm target: ```bash rustup default stable rustup update rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly ``` ## Test your set-up Now the best way to ensure that you have successfully prepared a computer for Substrate development is to follow the steps in [our first Substrate tutorial](https://docs.substrate.io/tutorials/v3/create-your-first-substrate-chain/). ## Troubleshooting Substrate builds Sometimes you can't get the Substrate node template to compile out of the box. Here are some tips to help you work through that. ### Rust configuration check To see what Rust toolchain you are presently using, run: ```bash rustup show ``` This will show something like this (Ubuntu example) output: ```text Default host: x86_64-unknown-linux-gnu rustup home: /home/user/.rustup installed toolchains -------------------- stable-x86_64-unknown-linux-gnu (default) nightly-2020-10-06-x86_64-unknown-linux-gnu nightly-x86_64-unknown-linux-gnu installed targets for active toolchain -------------------------------------- wasm32-unknown-unknown x86_64-unknown-linux-gnu active toolchain ---------------- stable-x86_64-unknown-linux-gnu (default) rustc 1.50.0 (cb75ad5db 2021-02-10) ``` As you can see above, the default toolchain is stable, and the `nightly-x86_64-unknown-linux-gnu` toolchain as well as its `wasm32-unknown-unknown` target is installed. You also see that `nightly-2020-10-06-x86_64-unknown-linux-gnu` is installed, but is not used unless explicitly defined as illustrated in the [specify your nightly version](#specifying-nightly-version) section. ### WebAssembly compilation Substrate uses [WebAssembly](https://webassembly.org) (Wasm) to produce portable blockchain runtimes. You will need to configure your Rust compiler to use [`nightly` builds](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html) to allow you to compile Substrate runtime code to the Wasm target. > There are upstream issues in Rust that need to be resolved before all of Substrate can use the stable Rust toolchain. > [This is our tracking issue](https://github.com/paritytech/substrate/issues/1252) if you're curious as to why and how > this will be resolved. #### Latest nightly for Substrate `master` Developers who are building Substrate _itself_ should always use the latest bug-free versions of Rust stable and nightly. This is because the Substrate codebase follows the tip of Rust nightly, which means that changes in Substrate often depend on upstream changes in the Rust nightly compiler. To ensure your Rust compiler is always up to date, you should run: ```bash rustup update rustup update nightly rustup target add wasm32-unknown-unknown --toolchain nightly ``` > NOTE: It may be necessary to occasionally rerun `rustup update` if a change in the upstream Substrate codebase depends > on a new feature of the Rust compiler. When you do this, both your nightly and stable toolchains will be pulled to the > most recent release, and for nightly, it is generally _not_ expected to compile WASM without error (although it very > often does). Be sure to [specify your nightly version](#specifying-nightly-version) if you get WASM build errors from > `rustup` and [downgrade nightly as needed](#downgrading-rust-nightly). #### Rust nightly toolchain If you want to guarantee that your build works on your computer as you update Rust and other dependencies, you should use a specific Rust nightly version that is known to be compatible with the version of Substrate they are using; this version will vary from project to project and different projects may use different mechanisms to communicate this version to developers. For instance, the Polkadot client specifies this information in its [release notes](https://github.com/paritytech/polkadot-sdk/releases). ```bash # Specify the specific nightly toolchain in the date below: rustup install nightly- ``` #### Wasm toolchain Now, configure the nightly version to work with the Wasm compilation target: ```bash rustup target add wasm32-unknown-unknown --toolchain nightly- ``` ### Specifying nightly version Use the `WASM_BUILD_TOOLCHAIN` environment variable to specify the Rust nightly version a Substrate project should use for Wasm compilation: ```bash WASM_BUILD_TOOLCHAIN=nightly- cargo build --release ``` > Note that this only builds _the runtime_ with the specified nightly. The rest of project will be compiled with **your > default toolchain**, i.e. the latest installed stable toolchain. ### Downgrading Rust nightly If your computer is configured to use the latest Rust nightly and you would like to downgrade to a specific nightly version, follow these steps: ```bash rustup uninstall nightly rustup install nightly- rustup target add wasm32-unknown-unknown --toolchain nightly- ``` --- ## File: env-setup/README.md # Env setup Special files for setting up an environment to work with the template: - `rust-toolchain.toml` when working with `rustup`. - `flake.nix` when working with `nix`. These files will be copied by the installer script to the main directory. They are put into this special directory to not interfere with the normal CI.