Skip to content

[WNS-56] OPP: bound the operator roster to the schedule window and cap queueout - #692

Merged
heifner merged 6 commits into
masterfrom
fix/wns-56-bounded-roster-and-queueout
Oct 24, 2026
Merged

heifner merged 6 commits into
masterfrom
fix/wns-56-bounded-roster-and-queueout

Conversation

@heifner

@heifner heifner commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes WIRE-406 (CertiK WNS-56) and WIRE-342. The OPERATORS roster listed every sysio.opreg row, and regoperator is self-authorized, so anyone with accounts could grow it until it no longer fit an envelope. buildenv then aborted inside sysio.epoch::advance, and every later advance failed the same way. Well before that, a roster with enough operators Ethereum had never seen ran its OPERATORS handler out of gas, so Ethereum could not apply it at all (WIRE-342).

  • finishadv builds the roster from the window: every operator its groups seat, with its current status and authex links. Outposts read the roster only to map those members to the keys they deliver with (Solana's epoch_in gate and quorum guard, Ethereum's window resolution), so nothing they use is lost. A seated member keeps its real status, so a slashed member still makes Solana refuse the window. An empty window queues no roster.
  • sysio.epoch::setconfig caps the window at the outposts' limits: groups of at most 7 (Ethereum's OPERATORS_PER_GROUP) and at most 8 of them (Solana's MAX_OPERATOR_GROUPS). That bounds the roster at 56 entries, about 5.6 KB. Ethereum applies the roster in one handler call: on wire-ethereum/next a 56-entry roster with 49 operators it has never seen dispatches in one transaction, 9.3M gas (12.4M with the outbound queue full) of EIP-7825's 16,777,216, where 8 × 16 runs out of handler gas. The separate window ceiling could no longer fail and is removed.
  • queueout rejects data over MAX_ATTESTATION_DATA_BYTES (32,232), the most an otherwise empty envelope carries. The producer's transaction fails, along with the state change it queued the row for, instead of a later buildenv. buildenv's two single-attestation checks are now unreachable and documented as invariants. The envelope size constants moved into the msgch header.
  • The docs and comments that described the full roster, the old 100/255/1000 ceilings or a 21-operator group now describe the window's roster and the 7 × 8 ceilings. At most 7 operators over 10 epochs is 70 payout credits, so the 10-epoch history bound is the cadence limit and the 100-credit cap stays only as a backstop.
  • No ABI change, so no SysioContractTypes regeneration, and no Ethereum or Solana change. Only sysio.epoch.wasm and sysio.msgch.wasm are rebuilt.

Notes

Tests

  • advance_roster_lists_only_the_window: 16 unbonded registrations, an unseated ACTIVE batch operator and an ACTIVE challenger stay out of the roster.
  • advance_roster_keeps_a_seated_slashed_member_until_it_leaves: a slashed member stays listed as SLASHED while seated and drops out once its group slides off.
  • advance_queues_no_roster_for_an_empty_window: with no schedule, no OPERATORS attestation and no envelope.
  • queueout_rejects_an_attestation_no_envelope_can_carry and buildenv_ships_an_attestation_at_the_cap_alone: one byte over the cap is refused; a row at the cap ships alone and the next row waits.
  • Updated: the setconfig ceiling tests (7 per group accepted and 8 rejected, and the largest window, 8 groups of 7), the emissions boundaries (cadence 10 accepted and 11 rejected at 7 per group, an 8-operator group rejected, and the legacy-cadence recovery at 7 per group), and [WNS-34] Fix WIRE-385 post-mutation operator roster publication #603's three roster assertions, where a slashed or terminated operator that has left the window is now absent.

contracts_unit_test passes 952/952 on current master.

// A row no envelope can carry would abort every later `buildenv`, and with it `sysio.epoch::advance`.
// Rejecting it here reverts only the producer's transaction, including the state change it queued
// the row for.
check(data.size() <= MAX_ATTESTATION_DATA_BYTES,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High — bound READY queue cardinality, not only each payload. This check prevents one oversized attestation, but buildenv still walks every READY row across all chains and copies every matching row before applying the envelope-byte budget. A holder-authorized sysio.synd::desyndicate creates one system-paid READY row per call, while min_desyndicate constrains value rather than row count. Enough individually valid rows can therefore starve later OPERATORS / BATCH_OPERATOR_GROUPS updates beyond the outpost schedule window, or exhaust finishadv resources and roll the transaction back with the backlog intact. Please add a chain-scoped ordered index plus a fixed scan/row budget, prioritize epoch-control attestations, and include a bounded recovery/prune path for existing backlog. This is the WIRE-415 path already disclosed in the PR body, but it remains material if this PR closes WNS-56/WIRE-406.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This PR stays scoped to WNS-56; the queue fix is WIRE-415, in #696, stacked on this one:

  • a chain-scoped READY index that puts OPERATORS and BATCH_OPERATOR_GROUPS ahead of everything else, so a backlog cannot hold back the schedule;
  • a buildenv walk with a fixed byte and row budget, so its cost no longer grows with the backlog;
  • queueout refusing non-schedule rows once a chain's READY backlog reaches a cap. desyndicate then reverts as a whole, burn included, and the holder retries later.

No prune path: a READY DESYNDICATE_LIQ is a payout whose LIQ is already burned, so dropping it would lose the holder's funds. The bounded walk drains a backlog one budget at a time.

@huangminghuang

Copy link
Copy Markdown
Contributor

Low — update documentation made stale by the window-only roster change. OVERVIEW.md:69 still says OPERATORS is the full roster; contracts/sysio.epoch/README.md:31 still advertises the old 100/255/1000 ceilings; docs/becoming-a-block-producer.md:34-36 says producer links are published in OPERATORS, although non-window producers are now omitted; and contracts/sysio.system/include/sysio.system/emissions.hpp:130-134 retains the old 21-operator cadence boundary. Please update these to describe the scheduled-window roster, the 8 × 16 = 128 limits, and the resulting cadence boundary.

@heifner

heifner commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in 2dcac2f. All four now describe the window's roster and the 8 × 16 = 128 limits, and the pay_cadence_epochs comment gives the new boundary: 6 at 16 per group, with the default 7-operator groups held to 10 by the history bound. The same 21-operator arithmetic was also in the MAX_PAY_CADENCE_EPOCHS comment, EMISSIONS.md and docs/contract-upgrade-order.md, so those changed too, along with the queueout row in the msgch README and the slashed-status note in docs/retired-opp-carriers.md.


entry.addresses.push_back(std::move(chain_addr));
++link_it;
ops_attest.operators.push_back(std::move(entry));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High — cap or chunk the roster to what Ethereum can dispatch atomically. The new limits permit 8 groups × 16 members, while finishadv encodes all 128 members into one OPERATORS attestation. Against current wire-ethereum/next (0b5184ce), I reproduced the maximum roster under the 16,777,216 transaction gas target: dispatch reverts on the first entry with OPP_HandlerGasExhausted (handler gas bound ≈13.94M). Ethereum resumes only between attestations, so when this atomic handler fails first, no cursor progress is committed and retries at the supported limit cannot advance the epoch. Please either lower the accepted roster ceiling, chunk OPERATORS, or make the Ethereum handler internally resumable, and add an 8×16 cross-repo gas-cap test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lowered, as you suggest: setconfig now takes at most 7 per group, Ethereum's OPERATORS_PER_GROUP, so the window and the roster are at most 8 × 7 = 56 (06ceae3). On wire-ethereum/next (0b5184ce), with every entry carrying an EVM key and an SVM address and every operator outside the delivering group unseen, it fits in one transaction:

Roster Peak transaction Result
8 × 7, 49 unseen 9,312,094 gas (55.5% of 16,777,216) dispatches in one transaction
the same, outbound queue full 12,422,290 (74.0%) one transaction
63 entries, 56 unseen, queue full 13,365,585 (79.7%) one transaction
8 × 16, 121 unseen — OPP_HandlerGasExhausted, as you found

The 8 × 7 case will be a permanent test in wire-ethereum's OPPWorstCase suite, in its own PR.

@valthon

valthon commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Measured this branch (2dcac2f) against the Solana outpost's heap, from the wire-solana side (SOL-392 / CertiK WNS-185). Short version: with this PR's roster bound, every depot envelope shape we measured fits the outpost's 256,000 B dispatch heap frame. Without it (master), a self-registered roster of a few hundred operators stalls Solana inbound permanently.

Method: the real liqsol_core.so in mollusk under its own bump allocator (which never frees), envelopes packed by a port of buildenv's estimator and trim loop, smallest working heap frame measured for both epoch_in finalize and the draining dispatch_attestations call. Harness: wire-solana#591, programs/liqsol-core/tests/harness/tests/depot_envelope_heap.rs.

What this branch bounds, as we read it: OPERATORS comes only from the schedule window, at most 8 × 16 = 128 operators; authex allows at most one link per chain kind (SVM 32 B, EVM 33 B); names are at most 12 characters. So the largest entry is 100 B and the largest roster 12,800 B, well under the 32,232 B queueout cap.

Drain-path results (budget 256,000 B), each with a full envelope of desyndications:

  • all-ACTIVE window at the per-entry maximum: 232,448 B (23,552 B headroom)
  • 112 of 128 seated members slashed/terminated: 229,376 B (26,624 B). This one needed an outpost change: the outpost logged one line per ineligible member (~240 B each, never freed), which put it at 258,048 B. wire-solana#591 now logs once per group.
  • 49 seated members without an SVM link (tightest): 249,856 B (6,144 B)
  • master's unbounded roster (489 entries): fails even at 262,144 B

Not measured: windows mixing slashed and SVM-less members, a desyndication that actually pays out (Token-2022 CPI), and byte-exact depot encoding (we used the Rust models encoder; sizes match zpp for these shapes).

One coupling to keep: the outpost's MAX_OPERATORS = 128 (opp_states.rs) must stay equal to MAX_SCHEDULED_BATCH_OPERATORS here. The harness pins the 128-entry case, so a change on either side should trip it.

static constexpr uint32_t MAX_SCHEDULED_BATCH_OPERATORS = 1000;
/// Upper bound on the number of batch-operator groups: Solana's operator registry seats at most
/// `MAX_OPERATOR_GROUPS` (8), and a window an outpost cannot seat stalls that outpost.
static constexpr uint32_t MAX_BATCH_OP_GROUPS = 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium — keep cluster-tool schedule validation in sync with these limits. wire-tools-ts still validates with MAX_BATCH_OP_GROUPS = 255, MAX_OPERATORS_PER_EPOCH = 100, and MAX_SCHEDULED_BATCH_OPERATORS = 1000 in BatchOperatorSchedule.ts. Configurations such as 9 groups of 1 or one group of 17 therefore pass tool preflight and fail later in epoch::setconfig during cluster startup. Please update those constants and their boundary tests in a companion change, ideally from a shared or generated source of truth, so invalid schedules are rejected before startup.

@huangminghuang huangminghuang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the blocking issues identified inline: the accepted 8×16 roster cannot currently be dispatched atomically by a fresh Ethereum outpost within the supported gas limit, and the cluster-tool preflight still accepts schedule configurations that the updated contract rejects. Please resolve or explicitly constrain these cross-repository boundaries before merging.

@heifner

heifner commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

@valthon Thanks, this is the measurement the Solana side needed. One change since: HM found Ethereum cannot apply a 128-entry roster in one handler call (OPP_HandlerGasExhausted), so #692 now caps groups at 7 and the window at 8 × 7 = 56 (06ceae3). That is inside Solana's limits (16 per group, 8 groups, 128 operators), so the coupling to keep is MAX_SCHEDULED_BATCH_OPERATORS ≤ MAX_OPERATORS, not equality.

A smaller roster leaves room for more desyndications (about 350 instead of 259), so I ran your harness from Wire-Network/wire-solana#591 at 8 × 7 (GROUP_SIZE 7, DEPOT_MAX_WINDOW_OPERATORS 56). Everything fits, with more headroom than at 8 × 16; the drain is the binding path:

Window, with a full set of desyndications Drain heap Headroom
56 at entry max, all ACTIVE 205,824 B 50,176 B
49 slashed 206,848 B 49,152 B
7 members without an SVM link (tightest) 208,896 B 47,104 B
42 without an SVM link 198,656 B 57,344 B

epoch_in finalize peaks at 62,464 B.

huangminghuang
huangminghuang previously approved these changes Oct 22, 2026

@huangminghuang huangminghuang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed current head 06ceae3 and its merge with the current base. The previous blocking findings are addressed: the 8 × 7 ceiling fits Ethereum’s atomic handler budget, and Wire-Network/wire-tools-ts#154 mirrors the contract validation. With the confirmed pre-launch/fresh-state rollout, no legacy-state migration is required. Current-head CI and the focused contract suites pass. Approved.\n\nWire-Network/wire-sysio#696 remains the disclosed stacked fix for the pre-existing READY-queue cardinality path and must land before deployment; #154 should likewise be coordinated before deployment.

heifner added a commit that referenced this pull request Oct 22, 2026
#692 caps groups at 7, so the lane test now mirrors 7 and builds the largest window at 8 x 7: 6,806 bytes, down from 15,446 at 8 x 16. `SCHEDULE_LANE_BYTES` stays 24 KiB, which still holds a window at Solana's 16 per group.
valthon
valthon previously approved these changes Oct 23, 2026
@huangminghuang

Copy link
Copy Markdown
Contributor

Low — correct the stale slash-propagation comment.

contracts/sysio.opreg/src/sysio.opreg.cpp:1292-1293 still says every outpost learns SLASHED from the next OPERATORS roster. This PR now emits OPERATORS only for the schedule window; once a slashed member's group slides off, that operator is omitted and the outposts learn the removal from BATCH_OPERATOR_GROUPS instead.

Please qualify the comment: a slashed member that remains seated is published as SLASHED; after it leaves the window, the schedule update removes it.

@heifner
heifner changed the base branch from fix/wire-385-stale-operator-rosters to master October 23, 2026 16:11
@heifner
heifner dismissed stale reviews from valthon and huangminghuang October 23, 2026 16:11

The base branch was changed.

(void)out(ops_attest);
// An empty roster tells an outpost nothing (Solana skips it as an anomaly), so an empty window
// queues none; the BatchOperatorGroups withhold below reports that state.
if (!ops_attest.operators.empty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High — an empty post-slide window preserves removed operators’ outpost authority.

This new guard suppresses the authoritative OPERATORS snapshot when the window becomes empty. On the same transition, finishadv also withholds BATCH_OPERATOR_GROUPS when the next group is empty (lines 891–939), so neither outpost receives a revocation.

This is reachable with the allowed 1-group/1-operator configuration. The sole non-bootstrap operator can deliver the current epoch and then request its full collateral withdrawal; opreg::withdraw immediately re-evaluates eligibility. The next slide removes that now-non-ACTIVE member and selects an empty tail. Previously, the full roster would have carried UNKNOWN/SLASHED/TERMINATED; this branch emits neither control attestation.

An initialized Solana outpost retains the old ACTIVE mapping and active group. Ethereum only clears an omitted/inactive resident seat when its OPERATORS handler runs, and its retained one-slot anchor can authorize that old address for the next epoch. With a 1×1 quorum, the removed key can submit a forged next envelope containing OPERATORS and BATCH_OPERATOR_GROUPS entries that re-seat itself.

Please provide an explicit fail-closed revocation shape that both outposts apply (for example bounded terminal tombstones or coordinated authoritative-empty support), or stop advancement when no authoritative next group can be conveyed. Add an initialized-outpost regression covering a nonempty→empty transition and assert that the old key cannot submit the next epoch. The new opreg slash comment is also still inaccurate on this path because the claimed group update is withheld.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I built the tombstone variant to test this: every roster listed the expired group's departing members with their real status. I also wrote initialized-outpost regressions for it on Ethereum and Solana. It doesn't close the attack, so I've left it out and squashed the PR.

  • Only the departing operator can deliver envelope N. Any revocation for N, whether a tombstone or an authoritative-empty, travels in envelope N. Both outposts admit only the group that envelope N−1 seated for N, which is the departing operator. Nothing else delivers it either: batch_operator_plugin relays only for the depot's front group (outpost_opp_job.cpp:54), and that group is empty after this slide.
  • A hostile operator can forge N whatever the depot emits. With a 1×1 quorum, its forged N re-seated it in both reproductions: a scratch run on Ethereum next and the Solana harness.
  • This predates [WNS-56] OPP: bound the operator roster to the schedule window and cap queueout #692. Master's full roster travelled in the same envelope.
  • Stopping advancement doesn't change what the outposts accept for N. With an empty window no one can deliver to the depot either, so the bridge halts at N anyway.

What happens to the old key after N:

  • Ethereum: the single-group window maps exactly one epoch ahead. With BATCH_OPERATOR_GROUPS withheld, N+1 is unmapped and the key reverts with OPP_UnmappedEpochGroup, tombstone or not.
  • Solana: the active group is not tied to an epoch, so the seated group stays authorized until a groups update arrives.

Closing this needs changes outside the roster, tracked in WIRE-423:

  1. Tie Solana's active group to the epoch it was shipped for, as Ethereum does.
  2. Keep a scheduled operator from withdrawing below its minimum until its scheduled epochs close.
  3. Elect a relay when the depot's window is empty.

The slash comment no longer claims the groups update. It now says rosters list the operator as SLASHED while it is seated and leave it out once its group has left.

The OPERATORS roster listed every sysio.opreg row, and regoperator is self-authorized, so anyone with accounts could grow it until it no longer fit an envelope. buildenv then aborted inside sysio.epoch::advance, and every later advance failed the same way (CertiK WNS-56, WIRE-406). Well before that, a roster with enough operators Ethereum had never seen ran its OPERATORS handler out of gas (WIRE-342).

Outposts read the roster only to map the window's members to the keys they deliver with, so finishadv now lists just the operators the window seats, each with its current status. setconfig caps the window at the outposts' limits, groups of at most 7 (Ethereum's OPERATORS_PER_GROUP) and at most 8 of them (Solana's MAX_OPERATOR_GROUPS), so the roster holds at most 56 entries. On wire-ethereum next, a 56-entry roster with 49 operators Ethereum has never seen dispatches in one transaction: 9.3M gas, 12.4M with the outbound queue full, of EIP-7825's 16,777,216.

queueout rejects data larger than an otherwise empty envelope can carry, so the producer's transaction fails instead of a later buildenv. buildenv's single-attestation checks are now unreachable invariants, and the envelope size constants moved into the msgch header.

The docs describe the window's roster and the 7 x 8 ceilings. At most 7 operators over 10 epochs is 70 payout credits, so the 10-epoch history bound is the cadence limit and the 100-credit cap stays only as a backstop.
@heifner
heifner force-pushed the fix/wns-56-bounded-roster-and-queueout branch from 139ec03 to 0e82d29 Compare October 23, 2026 20:31
Comment thread OVERVIEW.md Outdated
| Category | Types | Direction | Purpose |
|----------|-------|-----------|---------|
| **Operator Rosters** | `OPERATORS`, `BATCH_OPERATOR_GROUPS` | depot → outpost | Full operator roster and the next epoch's batch-operator group, sent every epoch |
| **Operator Rosters** | `OPERATORS`, `BATCH_OPERATOR_GROUPS` | depot → outpost | The operators the schedule window seats, with their chain addresses, and the next epoch's batch-operator group, sent every epoch |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low — qualify the empty-schedule exception. This says OPERATORS and BATCH_OPERATOR_GROUPS are “sent every epoch,” but finishadv now deliberately queues no OPERATORS when the post-slide window is empty and withholds BATCH_OPERATOR_GROUPS when the next group is empty. Please describe these as the normal non-empty-schedule updates, or mention the empty-window exception, so the overview does not promise control attestations that will not exist.

The overview said OPERATORS and BATCH_OPERATOR_GROUPS are sent every epoch, but an empty window sends no roster and an empty next group withholds BATCH_OPERATOR_GROUPS (HM's review).
huangminghuang
huangminghuang previously approved these changes Oct 23, 2026

@huangminghuang huangminghuang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the complete diff at fa823e2 against current master, including the documentation follow-up. I found no remaining actionable issues.

The previously raised empty-window authority concern is the pre-existing WIRE-423 recovery gap rather than a regression in this change. The follow-up changes only OVERVIEW.md, so the prior source, focused-test, ABI, and byte-for-byte WASM validation remains applicable. Exact-head GitHub CI currently has no failures; macOS is running and the Linux matrix is queued.

wire-tools-ts#154 is the compatible 7 x 8 configuration companion and must merge or co-deploy. The separate WIRE-415 queue-cardinality fix in #696 remains a deployment dependency and needs restacking/revalidation after this branch rewrite.

Brings in #658 (WNS-57 and WNS-51 emission headroom). The text files merged without a hunk both sides edited. sysio.epoch.wasm is rebuilt from the merged sources, and every other contract matches its committed wasm and ABI.
@huangminghuang

Copy link
Copy Markdown
Contributor

Low — documentation (contracts/sysio.system/EMISSIONS.md:28-29): budget is defined above as t5_distributable - t5_floor, so saying setemitcfg refuses a budget below t5_floor + total_distributed + pending_emission_amount double-counts t5_floor. The implementation correctly rejects t5_distributable < t5_floor + total_distributed + pending_emission_amount, equivalently budget < total_distributed + pending_emission_amount. Please change “a budget” to “a t5_distributable value,” or remove t5_floor from the budget-side expression.

Brings in #651, #659 and #701. sysio.epoch.wasm is rebuilt from the merged sources, and every other contract matches its committed wasm and ABI. MAX_OPERATORS_PER_EPOCH keeps the cap of 7 with #659's cost wording, one recorddel and termcheck per member. #659's shared rotation walk now checks the window roster: a member terminated as its group expires is no longer listed.
The budget is t5_distributable - t5_floor, so "refuses a budget below t5_floor + total_distributed + pending_emission_amount" counted the floor twice; setemitcfg refuses a t5_distributable below that sum (HM's review).
@heifner

heifner commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Low — documentation (contracts/sysio.system/EMISSIONS.md:28-29)

Fixed in 8594a73: setemitcfg refuses a t5_distributable below t5_floor + total_distributed + pending_emission_amount. The line came in with #658.

huangminghuang
huangminghuang previously approved these changes Oct 24, 2026

@huangminghuang huangminghuang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Targeted re-review completed through 8594a73. No blocking findings; the remaining P3 documentation clarification for the zero-active-outpost case is non-blocking.

Brings in #679 and #592. sysio.msgch.wasm is rebuilt from the merged sources, and every other contract matches its committed wasm and ABI. The sources merged without a hunk both sides edited.
@heifner
heifner merged commit a62f42c into master Oct 24, 2026
13 checks passed
@heifner
heifner deleted the fix/wns-56-bounded-roster-and-queueout branch October 24, 2026 18:35
heifner added a commit that referenced this pull request Oct 26, 2026
Brings in #692 (merged as a62f42c, superseding this branch's copies of its commits), #592, #659, #658, #651 and the rest of master.

- `buildenv` keeps #592's retention: shipped rows are erased once packed, the chain links come from the newest `byoutepoch` row, and the acknowledged-prefix sweep runs before the inbound PROCESSED sweep.
- `shipped_attestations` reads the newest `outenvelopes` row, since unacknowledged emits now stay, and the new readers decode with `fixture_abi_yield_function()` after #684's sweep.
- Two locals in `buildenv` are renamed so they no longer shadow (`by_queue`, `inbound`).
- `sysio.msgch.wasm` rebuilt.
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.

3 participants