Skip to content

v0.3.1 — voting + sigma-rust v6 opcode fixes

Choose a tag to compare

@mwaddip mwaddip released this 16 Apr 15:36

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

  • facts4dd4f20
  • chainbee0cc1
  • sigma-rust fork → 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/ergo v6.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