Proving v0: the app's prove setting, Proving tile and Set up hook; spec 7.7 (records, assignment, payout, activation as implemented); proving-v0 plan status; ledger P21 and P22; test script fixes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1251f0a1dd
commit
227b926fe4
12 changed files with 144 additions and 7 deletions
|
|
@ -52,6 +52,9 @@ pub struct Settings {
|
|||
/// switch in Settings shows the signing key's fingerprint).
|
||||
#[serde(default = "yes")]
|
||||
pub remote_jobs: bool,
|
||||
/// Prove the shards assigned to this machine's keys (src/prover.rs). Default off until the GPU numbers exist.
|
||||
#[serde(default)]
|
||||
pub prove: bool,
|
||||
}
|
||||
|
||||
fn one() -> u32 {
|
||||
|
|
@ -63,7 +66,7 @@ fn yes() -> bool {
|
|||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true }
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true, prove: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl Shared {
|
|||
st.mining.paused = settings.paused;
|
||||
st.mining.accepted_total = settings.accepted_total;
|
||||
st.address = address_state(&settings, &wallet_path);
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs };
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs, prove: settings.prove };
|
||||
st.live_page = packaged.live_page.clone();
|
||||
st.finality.message = "waiting for the miner".into();
|
||||
st.clock.hint = crate::platform::clock_hint().into();
|
||||
|
|
@ -224,6 +224,22 @@ impl Shared {
|
|||
Ok(json!({ "ok": true, "address": w.address, "display": keys::checksum(&w.address), "private_key": w.private_key, "wallet_file": self.wallet_path.display().to_string() }))
|
||||
}
|
||||
|
||||
/// The prover service switch (src/prover.rs); the thread picks it up within 10 s.
|
||||
pub fn set_prove(&self, on: bool) -> Result<Value, String> {
|
||||
{
|
||||
let mut s = self.settings.lock().unwrap();
|
||||
s.prove = on;
|
||||
}
|
||||
self.save_settings();
|
||||
let mut st = self.state.lock().unwrap();
|
||||
st.settings.prove = on;
|
||||
st.proving.enabled = on;
|
||||
if !on {
|
||||
st.proving.status = "off".into();
|
||||
}
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
|
||||
pub fn apply_settings(&self, identities: Option<u32>, vote: Option<bool>, login: Option<bool>, address: Option<&str>, display_name: Option<&str>) -> Result<Value, String> {
|
||||
let mut restart = Vec::new();
|
||||
{
|
||||
|
|
@ -488,6 +504,8 @@ impl Engine {
|
|||
));
|
||||
let v = crate::detect::node_version(&self.bins.node);
|
||||
self.st().node.version = v.clone();
|
||||
// the prover service (proving v0): its own thread, idle until the setting is on
|
||||
crate::prover::start(self.shared.clone(), self.bins.dir.clone());
|
||||
self.shared.log(&format!("node binary: {} ({v})", self.bins.node.display()));
|
||||
if self.ota.needs_rollback() {
|
||||
// this version died twice before it was healthy: the helper puts the previous one back
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ mod ota;
|
|||
mod update;
|
||||
mod jobs;
|
||||
mod jobrun;
|
||||
mod prover;
|
||||
|
||||
use std::io::{BufRead, Write};
|
||||
use std::sync::mpsc::channel;
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
let display_name = s("display_name");
|
||||
shared.apply_settings(identities, vote, login, address.as_deref(), display_name.as_deref())
|
||||
}
|
||||
"/api/prove" => shared.set_prove(body.get("on").and_then(|v| v.as_bool()).unwrap_or(false)),
|
||||
"/api/prove/setup" => crate::prover::setup(shared),
|
||||
"/api/update/check" => {
|
||||
shared.send(Cmd::CheckUpdate);
|
||||
Ok(json!({ "ok": true }))
|
||||
|
|
|
|||
|
|
@ -94,6 +94,29 @@ pub struct ProgramState {
|
|||
pub message: String,
|
||||
}
|
||||
|
||||
/// The prover service (src/prover.rs): assigned, proving, submitted, paid.
|
||||
#[derive(Clone, Serialize, Default)]
|
||||
pub struct ProvingState {
|
||||
pub enabled: bool,
|
||||
/// the host runs here (macOS, Linux) or inside WSL2 (Windows); false = Set up needed
|
||||
pub available: bool,
|
||||
pub setup_hint: String,
|
||||
pub backend: String, // cpu | cuda
|
||||
pub status: String, // off | setup | waiting | idle | proving | submitted
|
||||
pub keys: u32,
|
||||
pub assigned: u32,
|
||||
pub proved: u32,
|
||||
pub submitted: u32,
|
||||
pub paid: u32,
|
||||
pub failed: u32,
|
||||
pub paid_wei: u128,
|
||||
pub current: String,
|
||||
pub started_at: f64,
|
||||
pub last_prove_s: f64,
|
||||
pub last_paid: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Default)]
|
||||
pub struct FinalityState {
|
||||
pub last_lock: u64,
|
||||
|
|
@ -133,6 +156,8 @@ pub struct SettingsState {
|
|||
pub auto_update: bool,
|
||||
/// signed remote jobs from Igneum run on this machine (src/jobs.rs)
|
||||
pub remote_jobs: bool,
|
||||
/// the prover service (src/prover.rs)
|
||||
pub prove: bool,
|
||||
}
|
||||
|
||||
/// One remote job this machine ran (the ledger entry), for the Settings history and the last-job strip.
|
||||
|
|
@ -236,6 +261,7 @@ pub struct State {
|
|||
pub mining: MiningState,
|
||||
pub program: ProgramState,
|
||||
pub finality: FinalityState,
|
||||
pub proving: ProvingState,
|
||||
pub clock: ClockState,
|
||||
pub address: AddressState,
|
||||
pub settings: SettingsState,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@
|
|||
$('update-open').addEventListener('click', function () { api('api/update/open', {}); });
|
||||
$('update-later').addEventListener('click', function () { $('update-banner').hidden = true; $('update-banner').dataset.dismissed = (state && state.update) ? (state.update.status + ':' + state.update.version) : '1'; layoutBanners(); });
|
||||
$('job-hide').addEventListener('click', function () { $('job-banner').hidden = true; $('job-banner').dataset.dismissed = jobKey(state && state.jobs); layoutBanners(); });
|
||||
$('s-prove').addEventListener('change', function () { api('api/prove', { on: this.checked }).then(function (r) { if (r.ok) toast(r.ok && $('s-prove').checked ? 'Proving on; the first shard arrives within a minute' : 'Proving off'); }); });
|
||||
$('pv-setup').addEventListener('click', function () { api('api/prove/setup', {}).then(function (r) { toast(r.ok ? 'Setup started in its own window' : (r.error || 'could not start')); }); });
|
||||
$('s-jobs-allow').addEventListener('change', function () { api('api/jobs/allow', { on: this.checked }); setTimeout(fillSettings, 800); });
|
||||
$('s-jobs-check').addEventListener('click', function () { api('api/jobs/check', {}); $('s-jobs-note').textContent = 'Checking.'; setTimeout(fillSettings, 4000); });
|
||||
|
||||
|
|
@ -358,6 +360,15 @@
|
|||
// finality
|
||||
if (f.last_lock > 0) { $('f-lock').textContent = '#' + withCommas(f.last_lock); $('f-age').textContent = rel(f.age_s); $('f-note').textContent = 'Checkpoints lock at 2/3 of the 30-day weight. Votes are sent by this miner.'; }
|
||||
else { $('f-lock').textContent = 'none yet'; $('f-age').textContent = 'n/a'; $('f-note').textContent = f.message || 'Locks appear once the miner votes on checkpoints.'; }
|
||||
// proving
|
||||
var pv = s.proving || {};
|
||||
var pvWord = { off: 'off', setup: 'needs setup', waiting: 'waiting', idle: 'idle', proving: 'proving', submitted: 'submitted' }[pv.status] || (pv.status || 'off');
|
||||
$('pv-state').textContent = pvWord + (pv.status === 'proving' && pv.current ? ' ' + pv.current : '') + (pv.backend && pv.enabled && pv.available ? ' (' + pv.backend.toUpperCase() + ')' : '');
|
||||
$('pv-assigned').textContent = String(pv.assigned || 0);
|
||||
$('pv-submitted').textContent = String(pv.submitted || 0);
|
||||
$('pv-paid').textContent = String(pv.paid || 0) + (pv.paid_wei ? ' (' + (Number(pv.paid_wei) / 1e18).toFixed(4) + ' IGN)' : '');
|
||||
$('pv-note').textContent = pv.enabled ? (pv.message || '') : 'Off. Settings switches it on: this machine proves the shards the chain assigns to its keys.';
|
||||
$('pv-setup-row').hidden = !(pv.enabled && !pv.available && pv.setup_hint);
|
||||
$('f-votes').textContent = withCommas(f.votes);
|
||||
// events
|
||||
var key = s.events.length ? s.events[0].t + ':' + s.events.length : '';
|
||||
|
|
@ -498,6 +509,7 @@
|
|||
}
|
||||
function fillJobsSettings(j) {
|
||||
$('s-jobs-allow').checked = !!j.allowed;
|
||||
$('s-prove').checked = !!(state.settings && state.settings.prove);
|
||||
$('s-jobs-key').textContent = j.key_fingerprint ? 'signing key sha256:' + j.key_fingerprint : '';
|
||||
var parts = [];
|
||||
if (!j.allowed) parts.push('Off: nothing runs here until it is switched on.');
|
||||
|
|
|
|||
|
|
@ -201,6 +201,17 @@
|
|||
</div>
|
||||
<p class="note" id="f-note">Locks appear once the miner votes on checkpoints.</p>
|
||||
</div>
|
||||
<div class="tile" id="tile-proving">
|
||||
<div class="h">Proving</div>
|
||||
<div class="kv">
|
||||
<div><span class="k">state</span><span class="v mono" id="pv-state">off</span></div>
|
||||
<div><span class="k">assigned</span><span class="v mono" id="pv-assigned">0</span></div>
|
||||
<div><span class="k">submitted</span><span class="v mono" id="pv-submitted">0</span></div>
|
||||
<div><span class="k">paid</span><span class="v mono" id="pv-paid">0</span></div>
|
||||
</div>
|
||||
<p class="note" id="pv-note">Off. Settings switches it on: this machine proves the shards the chain assigns to its keys.</p>
|
||||
<div class="row" id="pv-setup-row" hidden><button class="btn small" id="pv-setup">Set up</button></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Events</h3><div class="eyebrow">newest first</div></div>
|
||||
<div class="feed" id="d-events"><div class="empty">No events yet.</div></div>
|
||||
|
|
@ -264,6 +275,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label class="switch"><input type="checkbox" id="s-vote"><span class="track"></span><span>Vote on finality checkpoints</span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-prove"><span class="track"></span><span>Prove assigned shards (proving v0; on a Mac the CPU prover is slow)</span></label>
|
||||
<label class="switch"><input type="checkbox" id="s-login"><span class="track"></span><span>Start at login</span></label>
|
||||
</div>
|
||||
<div class="field">
|
||||
|
|
|
|||
|
|
@ -1478,6 +1478,22 @@ Answer: Correct on every point, and measured first by our own attack run (`sim/d
|
|||
|
||||
Evidence: `docs/bench-log.md`, 4 October 2026 "difficulty rule: timestamp attack fixed"; `docs/analysis/difficulty-2026-10-03.md` addendum (before and after table); `sim/difficulty/attacks/README.md`; unit tests `igneum::tests::sanitised_clock_telescopes_a_forged_stamp`, `difficulty::tests::igneum_clock_steps_pay_a_forgery_back`, `difficulty::tests::igneum_flood_at_85_blocks_per_second_stops_at_the_minimum_target`, `difficulty::tests::both_rules_share_the_target_floor`. Review ids: attack README scenarios 3 and 7.
|
||||
|
||||
## Proving v0 findings (4 October 2026, afternoon): stated, not fixed
|
||||
|
||||
### P21. The SP1 proof is not what consensus checks in proving v0
|
||||
"Your proof records pay provers, and the node pays a record whose statement matches its own execution whether or not the SP1 proof behind it verifies. A prover can sign the native statement without proving anything."
|
||||
|
||||
Status: Open, stated in spec 7.7 item 4 (4 October 2026). Branch `proving` of the fork, `igneum/exec/src/proving.rs`.
|
||||
|
||||
Answer: Correct for v0, and by design for now. The consensus check on a carried record is the native-execution veto (spec 7.2 item 5): the statement must equal the node's own 328-byte shard statement for that shard and payout address, so no record can move state or pay for a wrong claim. The SP1 proof is verified off the consensus path by the proof pool's verifier (`igneum-prove-host --mode verify`), and a producer offers only verified records to its templates; a producer that includes unverified records (trust mode, test networks only) can pay a prover who did not prove. The damage is bounded to that prover's payout. What closes it: the aggregated segment record of design 5.4 verified in consensus, which needs the SP1 verifier inside the node (the SDK dependency the node does not carry today) or a bounded in-consensus verification budget. Until then the devnet runs v0 with every producer verifying.
|
||||
|
||||
### P22. The rewards and payouts are inputs to the shard proof, not outputs
|
||||
"The shard guest takes the segment's rewards and the prover payouts as data and commits the post-root after them. A host can feed any list and the proof still verifies."
|
||||
|
||||
Status: Open, stated in spec 7.7 item 6 (4 October 2026).
|
||||
|
||||
Answer: Correct, and already true of the rewards since devnet v4 (`BlockFixture.rewards`, `proving_pool_credit`): the shard statement is "from this pre-root, these transactions, these rewards and payouts, the post-root is X". The node checks the statement against its own execution, which used the rewards and payouts consensus derived, so a proof over a different list does not match any node's statement and pays nothing. Closing it in the proof itself means the aggregator deriving the rewards and payouts from consensus data it verifies (the mergeset's blue blocks and the carried records), which is the consensus-proof work of design section 7.
|
||||
|
||||
## Status updates, 4 October 2026 (branch fin-fixes, commit da1eb889)
|
||||
|
||||
- **F17** (keys are free, the draw is per key). Status: Fixed in the node (4 October 2026). `is_aggregator` draws the 8 aggregators by weight, `output x total < 8 x weight x 2^64`, so a splitter holds the tickets its weight buys and no more; spec 3.10 S1 row; unit test `sortition_is_by_weight_not_key_count` (200 dust keys plus 6 real ones); attack harness scenario 2 re-run: honest keys drew 1.61 seats per crowded checkpoint against 1.55 expected by weight, where master drew 0.32 against 0.33 per key (`docs/bench-log.md`, "finality fixes F17 and F1"). Still open from this entry: the client's one-key default, S2 (O-3.5), the bitmap size (O-3.12). Was: Rule fixed (spec 7.2 and W6), node per key.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,25 @@ Implemented 4 October 2026 (`proving/igneum-prove`, commits "Proving: shard cutt
|
|||
|
||||
Reading. The modexp-heavy shard costs 9 SP1 cycles per pgas against the unit's 1,000: the prototype table's modexp entry (1,000 + 10 per input byte) is two orders of magnitude above its SP1 cost, which is the R1 calibration in one number; at the current table a shard of `S_p` is about 60 M cycles, far below what the unit would imply. The witness is small because the devnet state is small; the share of the shard's cycles spent on the trie is not isolated yet (R3). The CPU times are on a machine at load 40 shared with the live devnet and other agents' builds; they are correctness runs, not throughput.
|
||||
|
||||
## Proving layer v0 in the node and the app (4 October 2026, afternoon)
|
||||
|
||||
Implemented on the fork branch `proving` (`vendor/igneum-node-proving`, from devnet-v4 3bfe346f), in `proving/igneum-prove` and in `app/igneum-app/src/prover.rs`; the rules are spec 7.7. Status words as above.
|
||||
|
||||
| Piece | Status | Where |
|
||||
|---|---|---|
|
||||
| Proof record (per shard, BLS-signed by the vote key), coinbase record section `IGNP` before the finality section, p2p message 71 at protocol 13, shard id, sortition draw, payout arithmetic | Implemented, unit-tested (record round trip and signature, nested sections, sortition determinism and weighting, dust, payouts) | `consensus/core/src/proving.rs`, `protocol/flows/src/v10/proving.rs`, `v13/` |
|
||||
| `proving_v0_activation_daa` height switch (default never on every network; the override file carries it) | Implemented, unit-tested | `consensus/core/src/config/params.rs`, `infra/fast-time/override-60x.json` |
|
||||
| The node's shard plan: per-transaction boundaries with the carry of 7.6 and the state root, the cut of `igneum_prove_core::plan`, the sortition window from the finality parameters, assignees per shard | Implemented, unit-tested against the core planner's test vector; the exporter's plan checked equal to the node's on the test network | `igneum/exec/src/executor.rs` (boundaries), `igneum/exec/src/proving.rs` |
|
||||
| Native shard statement (the 328-byte public values recomputed for any payout address), the record checks (chain membership, record window, plan, signature, assignment inside the exclusive window, native-execution veto) | Implemented, unit-tested | `igneum/exec/src/proving.rs` |
|
||||
| Proof pool with the external verifier (`igneum-prove-host --mode verify`), trust mode for test networks, template section of verified records, payout at the carrying segment, reorg unwinding | Implemented; pool unit-tested; verifier and payout exercised on the test network | `igneum/exec/src/proving.rs`, `service.rs` |
|
||||
| RPCs `igneum_getShardPlan`, `igneum_getProofRecords`, `igneum_submitProofRecord`, `igneum_getAssignedShards`, `igneum_getProvingStatus`; `igneum_exportSegments` carries payouts | Implemented | `igneum/exec/src/rpc.rs` |
|
||||
| Payouts in the shard statement (fixture, `ShardInput`, `execute_range`, the guest), the empty-segment plan fix, host modes `compressed` (execute + compressed, statement and proof hash in the results) and `verify` | Implemented; guest rebuilt (new shard vk) | `proving/igneum-prove` |
|
||||
| `igneum-miner vmine` (voting producer for PoW-less test networks, with an EVM payout address), `sign-record`, `key-hash` | Implemented | `igneum/miner/src/proving.rs` |
|
||||
| The app's prover service: `prove` setting (default off), the loop (work list, export, cut, prove, sign, submit), the Proving tile (assigned, proving, submitted, paid), WSL2 detection and Set up on Windows, CPU on macOS | Implemented; loop logic unit-tested; NOT run end to end inside the app yet | `app/igneum-app/src/prover.rs` |
|
||||
| 3-node test network on 29800+ at 60x with activation 60 | Implemented, run; see the bench-log entry of 4 October 2026 (afternoon) for what it showed | `tools/proving-v0/run.mjs` |
|
||||
|
||||
Decisions the implementation forced, all in spec 7.7: per-shard records instead of the aggregated record of design 5.4 (the aggregated record is the next step); the SP1 proof verified off the consensus path, the native statement being the consensus check; payouts as data in the shard statement beside the rewards; an empty segment is one shard ending at the root after rewards and payouts.
|
||||
|
||||
## What waits for the GPU
|
||||
|
||||
`PROVE-SHARD.bat` on the RTX 5090: the shard at `S_p` in the three stages and the two- and four-shard blocks end to end. The RESULT lines fill the GPU row of the bench-log entry and give the first point for `S_p`. The P20 Drop fix and the gap before the first compressed stage are confirmed or not by the same run.
|
||||
|
|
@ -41,7 +60,8 @@ Reading. The modexp-heavy shard costs 9 SP1 cycles per pgas against the unit's 1
|
|||
| Shard time on a 12 GB card (the phase 2 gate, ledger P1, overclaim 27) | the 5090 run is pending; no 3060-class card has run it | PROVE-SHARD.bat, then the 3060-class card |
|
||||
| The chain rule measured (segment N verifies N-1) | in the guest and the proof system, no host mode for two consecutive fixtures yet | next step: `--mode chain` over consecutive blocks |
|
||||
| The Groth16 or Plonk wrapper for light clients (ledger P3, overclaim 25) | not run; needs SP1's circuit artifacts and a measurement on consumer hardware | R4, phase 2 benchmark |
|
||||
| Sortition, proof records, the native-execution veto in the node | the node has no proof records on devnet v4 | design 5.4, 5.5; the stub `ProofSystem` path exists in the host |
|
||||
| The aggregated segment record and the chain rule in consensus | proving v0 carries per-shard records and verifies the SP1 proof off the consensus path (spec 7.7 items 1 and 4) | the aggregator's record on top of the shard records; verification of the aggregated proof in consensus |
|
||||
| Proving on the GPU through the app | the app's prover loop is written and unit-tested; the WSL2 path has never run | PC 2 after the Windows exe ships (OTA) |
|
||||
| pgas calibration (R1) | the table is the prototype; 9 cycles per pgas on modexp, 1,400 to 1,600 on a plain transfer shard | calibrate per opcode in SP1 with three input sizes |
|
||||
| Trie share of pgas (R3) | not isolated | instrument the guest |
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ Nothing in consensus changes for any of this: the segment claim already commits
|
|||
| Mempool bounds | `gas_limit <= B_e`; estimated pgas `<= B_p`; template packs by the estimate | Designed, node policy (ledger P18, P19), 7.5 |
|
||||
| Shard budget `S_p` | 7,500,000 pgas (`B_p / 4`), provisional: the specification carried no number before 4 October 2026; set from the shard-time measurements, never from the fixtures | Implemented (`proving/igneum-prove/core/src/config.rs`), value Designed, 7.6 |
|
||||
| Shard statement carry | a link hash between consecutive shards (transaction index, including block, block gas and pgas, cumulative gas, running transaction commitment) | Implemented, 7.6 |
|
||||
| Proof record (v0) | per shard, 274 bytes, BLS-signed by the prover's vote key, in the coinbase extra data before the finality section; proof bytes on p2p message 71 | Implemented, 7.7 |
|
||||
| Record window | 600 chain blocks behind the carrier | Implemented, 7.7 (Designed value) |
|
||||
| Records per block | 8 | Implemented, 7.7 (Designed value) |
|
||||
| Payout per shard | the segment's pool credit in equal parts, remainder to shard 0, paid by the carrying segment | Implemented, 7.7 |
|
||||
| Proving v0 activation | `proving_v0_activation_daa`, default never | Implemented, 7.7 |
|
||||
|
||||
## 7.5 Proving gas per transaction: the cap and the abort
|
||||
|
||||
|
|
@ -92,3 +97,18 @@ Added 4 October 2026 because the implementation (`proving/igneum-prove`, devnet
|
|||
1. **Carry between shards.** Section 7.2 cuts a segment at transaction boundaries, but a transaction's outcome depends on more than the state: the including block's running gas and pgas (the budgets of 7.5), the receipts' cumulative gas, and the position in the segment. Shard i therefore starts from a carry (transaction index, including-block index, block gas, block pgas, cumulative gas, running transaction commitment) and ends with the carry shard i+1 starts from; each shard proof commits the hash of both (`link_in`, `link_out`), and the block proof is invalid unless every `link_in` equals the previous `link_out` and the first is the empty carry. A shard whose single transaction exceeds `S_p` is a shard of its own (`over_budget`); the zkVM's own continuations prove it (approximate).
|
||||
2. **Transaction commitment.** The block's transaction commitment is a running keccak over the transactions in sequence order (`acc = keccak(acc, miner, blue, length, raw)`), carried in the link, so it is the same whatever the cut. The block proof's `tx_commitment` is the final accumulator.
|
||||
3. **Receipts.** A shard commits the ordered trie root of its own receipts (cumulative gas running across the whole segment); the block proof commits keccak over the shard receipts roots in order. This is what a proof record's `receipts` (design 5.4) means under this implementation, and what the native-execution veto of 7.2 item 5 compares. The provers are committed the same way (keccak over the shards' payout addresses, ledger P12), beside the shard program's verifying-key hash and, when the proof chains to the previous segment's proof, the aggregator's own.
|
||||
|
||||
## 7.7 Proof records, assignment and payout, as implemented (proving v0)
|
||||
|
||||
Added 4 October 2026 because the implementation (`vendor/igneum-node-proving`, branch `proving`; `proving/igneum-prove`; `app/igneum-app/src/prover.rs`) needed decisions the specification did not give. Every item is Implemented on the proving branch and behind the height switch of item 7; nothing here changes the devnet until the switch is set. Where this section and the design document's section 5.4 differ, this section is what runs.
|
||||
|
||||
1. **The record is per shard, not per segment.** Design 5.4 carried one aggregated record per segment with a prover list. Proving v0 carries one `ProofRecord` per shard (`kaspa_consensus_core::proving`): version, chain block hash and height, shard index, the prover's BLS vote key, the payout address, the statement (keccak256 of the shard program's 328-byte public values, 7.6), the SHA-256 of the proof bytes, and a BLS signature over all of it under the tag `IGNEUM_PROOF_RECORD_V1`, domain-separated with the network name. 274 bytes. The aggregated segment proof and its chain rule (design 5.3) are not in consensus yet: the per-shard record is what the devnet can produce today, and the aggregated record of design 5.4 is the next step on top of it (an aggregator's record would name the same shards).
|
||||
2. **Carriage.** Records ride in the coinbase extra data as a section `records || len_le32 || "IGNP"` placed before the finality section (`"IGNF"` closes the extra data), at most 8 per block. The proof bytes do not ride in blocks: they travel beside the record on the p2p message `IgneumProofRecordMessage` (type 71, protocol version 13; peers at 12 never receive it) and through `igneum_submitProofRecord`, committed by the record's `proof_hash`.
|
||||
3. **What every node checks on a carried record (consensus).** The record names a chain block on the executor's own chain at most 600 chain blocks behind the carrier; its shard index is in that segment's plan; its signature verifies under its key; its statement equals the node's native statement for that shard and payout address (the veto of 7.2 item 5, computed by every node from its own trace); and, while the carrier's DAA score is within 10 of the segment's, its key is one of the shard's 8 assignees (7.2 items 3 and 4). A record that fails is ignored, not a fault of the block: it pays nothing. The SP1 proof is NOT verified on this path (item 4).
|
||||
4. **Verification is off the consensus path.** A node keeps a proof pool: records with their proof bytes, accepted after the checks of item 3 against its own execution. A verifier (`igneum-prove-host --mode verify`, SP1 compressed proof against the shard program's verifying key, the statement compared) runs one proof at a time outside consensus; a block producer offers only verified records to its templates. A node without a verifier relays and stores records and never includes them. Consequence, stated plainly: a prover who signs a correct statement with a fake proof is paid if a producer includes the record without verifying; honest producers verify, and the native statement bounds the damage to that prover's payout, never to state. The aggregated segment proof verified in consensus (design 5.4) closes this and is the next step.
|
||||
5. **Assignment, as implemented.** The sortition window at chain block C is the vote key hash of every blue block in C's past with DAA score in `(daa(C) - W, daa(C)]`, in GHOSTDAG order (the executor's segment order), restricted to keys with at least `dust` such blocks (W and dust are the finality parameters of section 3). The draw is `H("igneum-shard/" || epoch_seed || shard_id || n)` with H the chain's BLAKE2b under the domain `IgneumShardSortition`, read as the low 16 bytes little-endian, modulo the window length; `shard_id = BLAKE2b_IgneumShardId(block hash || index_le32)`; `epoch_seed` is the executor's epoch seed of the segment (the devnet's chain-block seed, not yet the VDF of section 4). The draw stops at 8 distinct keys or when every eligible key holds a slot. The list is in every node's `igneum_getShardPlan`.
|
||||
6. **Payout, as implemented.** The 20% pool share of a segment (section 5.3) is credited to the pool escrow when the segment executes, as before. At the carrying chain block C, for every record its segment's blocks carry in sequence order, the first valid record per (segment, shard) pays that shard's part of the segment's credit, equal parts with the remainder to shard 0, from the escrow to the record's payout address, as a state transition of C's segment applied after the rewards and before the transactions (design 1.1: rewards by rule). The shard statement commits the post-root after those payouts, so the fixture, the shard input and the guest carry the segment's payouts as data beside the rewards (`payouts`), the same class of unverified input as the rewards are in v0. The UTXO-side coinbase is unchanged (its pool output still burns). A reorg unwinds the payouts with the carrier.
|
||||
7. **Activation.** `Params::proving_v0_activation_daa` (override file, default never) gates item 6 only: before it, records are relayed, stored, carried and checked, and nothing is paid; from it, carriers pay. Every node must run the proving build before the height (a node on the old build neither carries nor pays, and a block whose coinbase carries a record section is valid to it, since the section is extra data it does not read).
|
||||
8. **The plan.** Every segment has at least one shard; an empty segment is one shard whose statement applies the rewards and payouts only. The node cuts from its own per-transaction boundaries (`TxBoundary`: the carry of 7.6, the state root after every transaction) with the cut of `igneum_prove_core::plan`; `igneum-prove-export` must reproduce the node's plan on the same export, and the test network checks that it does.
|
||||
|
||||
RPCs (the execution layer's JSON-RPC): `igneum_getShardPlan(block)`, `igneum_getProofRecords(block)`, `igneum_submitProofRecord({record, proof})`, `igneum_getAssignedShards([keyHash...], lookback)` (the prover's work list), `igneum_getProvingStatus()`. Signing without the BLS key material in the prover process: `igneum-miner sign-record` and `key-hash`.
|
||||
|
|
|
|||
1
proving/igneum-prove/Cargo.lock
generated
1
proving/igneum-prove/Cargo.lock
generated
|
|
@ -2811,6 +2811,7 @@ dependencies = [
|
|||
"igneum-evm-types",
|
||||
"igneum-prove-core",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sp1-build",
|
||||
"sp1-sdk",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ const EXPORT = `${PROVE}/igneum-prove-export`;
|
|||
const TMP = '/tmp/igneum-proving-v0';
|
||||
const BASE = 29800, SUFFIX = 955, CHAIN_NAME = `igneum-devnet-${SUFFIX}`, CHAIN_ID = 4463;
|
||||
const args = process.argv.slice(2);
|
||||
const SECS = +(args[args.indexOf('--secs') + 1] || 1500);
|
||||
const ACTIVATION = +(args[args.indexOf('--activation') + 1] || 60);
|
||||
const flag = (name, dflt) => { const i = args.indexOf(name); return i >= 0 && args[i + 1] !== undefined ? +args[i + 1] : dflt; };
|
||||
const SECS = flag('--secs', 1500);
|
||||
const ACTIVATION = flag('--activation', 60);
|
||||
const EMPTY = args.includes('--empty');
|
||||
const started = [];
|
||||
const t0 = Date.now();
|
||||
|
|
@ -45,7 +46,12 @@ const PAYOUT = '0x4242424242424242424242424242424242424242';
|
|||
|
||||
rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true });
|
||||
const override = `${TMP}/override.json`;
|
||||
writeFileSync(override, JSON.stringify({ ...JSON.parse(readFileSync(FILE, 'utf8')), skip_proof_of_work: true, proving_v0_activation_daa: ACTIVATION }));
|
||||
// text edits, not JSON.parse: the file carries u64::MAX values that JavaScript numbers cannot hold
|
||||
const overrideText = readFileSync(FILE, 'utf8')
|
||||
.replace(/"proving_v0_activation_daa":\s*\d+/, `"proving_v0_activation_daa": ${ACTIVATION}`)
|
||||
.replace(/"skip_proof_of_work":\s*(true|false)/, '"skip_proof_of_work": true');
|
||||
if (!/"skip_proof_of_work": true/.test(overrideText) || !new RegExp(`"proving_v0_activation_daa": ${ACTIVATION}`).test(overrideText)) throw new Error('override edit failed');
|
||||
writeFileSync(override, overrideText);
|
||||
|
||||
class Node {
|
||||
constructor(i, connect = [], env = {}) {
|
||||
|
|
@ -59,7 +65,7 @@ class Node {
|
|||
const a = ['--devnet', `--devnet-suffix=${SUFFIX}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', '--unsaferpc',
|
||||
`--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--rpclisten-json=127.0.0.1:${this.jsonPort}`, `--evm-rpclisten=127.0.0.1:${this.evmPort}`,
|
||||
`--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes'];
|
||||
if (this.connect.length) a.push(`--connect=${this.connect.join(',')}`); else a.push('--outpeers=0');
|
||||
if (this.connect.length) a.push(...this.connect.map(c => `--connect=${c}`)); else a.push('--outpeers=0');
|
||||
const out = openSync(this.logFile, 'a');
|
||||
this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, ...this.env } });
|
||||
started.push(this.proc);
|
||||
|
|
|
|||
Loading…
Reference in a new issue