v0.3.1 — voting + sigma-rust v6 opcode fixes
Mainnet sync fixes: unblocks two consensus stalls latent in v0.3.0 (voting-epoch boundary at h=1,628,160 and sigma-rust parse-time type check at h=1,711,120), plus an indexer addon fix for the token-deduplication crash at h=500,117.
Full mainnet resync from genesis to tip was verified on v0.3.1 — no checkpoint needed.
Fixes
fix(voting) — rule-409 guard on SubblocksPerBlock auto-insert (#1a60129)
compute_expected_parameters now takes the boundary block's ErgoValidationSettingsUpdate payload (extension key [0x00, 124]) as a second argument and uses it to gate the BlockVersion == 4 SubblocksPerBlock auto-insert. Mainnet's LaunchParameters.proposedUpdate has carried rule 409 (exMatchParameters) since genesis; the JVM reference skips the auto-insert at the v6 activation boundary because rule 409 is being activated in the same update. Our prior unconditional insert produced 12 parameter-table entries where the on-chain block carries 11, and sync stalled deterministically at h=1,628,160.
fix(voting) — matchParameters60 proposedUpdate check at BlockVersion ≥ 4 (#122cd3c)
Full parity with JVM Parameters.matchParameters60: chain now tracks active_proposed_update_bytes as live state (seeded from LaunchParameters.proposedUpdate, advanced atomically with active_parameters at every accepted epoch-boundary block). check_parameters_v6 compares the block's ID 124 payload byte-for-byte against the chain's tracked value — gated on BlockVersion ≥ 4 to match JVM's blockVersion < Interpreter60Version short-circuit. Pre-v4 boundaries (1 through 1,627,136 on mainnet) skip the check entirely, which is required because pre-v6 on-chain ID 124 bytes carry statusUpdates the chain crate does not currently model.
fix(sigma-rust fork → d8a588a1) — parse-time SOption(T) leniency in check_post_eval_tpe
check_post_eval_tpe now accepts SOption(T) where T is expected, matching JVM OneArgumentOperationSerializer.parse's unchecked asValue[T] cast at deserialization time. Real-world scripts on mainnet feed getVar[Coll[Byte]](id) (post-eval type SOption(SColl(SByte))) directly into blake2b256 without an explicit .get; our stricter parse rejected mainnet tx dce90de1… at h=1,711,120. Evaluation-time behavior is unchanged — scripts that actually execute that path still fail via EvalError::UnexpectedValue, same as JVM runtime.
fix(indexer) — dedupe box tokens and retry initial node connection (#19b2f6e)
A box may list the same token_id multiple times on the wire (legal in Ergo). The indexer's (box_id, token_id) PK rejected the second row, killing the indexer on block 500,117 in an unbreakable systemd restart loop. Parser now sums amounts per token_id within each output, matching explorer semantics. Separately, the startup GET /info call had no retry — any node hiccup exited the process with status 1 and let systemd respawn. Now backs off 5s → 60s indefinitely.
Submodule pins
facts→4dd4f20chain→bee0cc1sigma-rustfork →d8a588a1
Verification
- Mainnet resync from genesis → tip (1,765,344+) clean.
- 270+ unit tests across
enr-chain,ergo-validation,ergo-sync,ergo-node-rust. - JVM cross-reference against
ergoplatform/ergov6.0.3 for every voting consensus change.
Upgrade notes
Operators running v0.3.0 on mainnet would have stalled at h=1,628,160 (stuck deterministically, no state corruption); they can upgrade in place and sync resumes. Fresh operators starting from genesis should use v0.3.1 directly — v0.3.0 cannot cross either boundary without this release.
🤖 Generated with Claude Code
Full Changelog: v0.3.0...v0.3.1