Skip to content

feat(rate-limit): public From<&RateLimits> for OpcodeRateLimits - #7

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
feat/rate-limit-from-chia
Aug 11, 2026
Merged

feat(rate-limit): public From<&RateLimits> for OpcodeRateLimits#7
MichaelTaylor3d merged 3 commits into
mainfrom
feat/rate-limit-from-chia

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

DO NOT MERGE — gate round pending

Closes the untestability of OpcodeRateLimits for consumers (dig_ecosystem#2228, dig-gossip's port of tests/con_005_tests.rs).

What changed

Purely additive. The re-key that derives the opcode table from a Chia RateLimits was private (fn from_chia, src/rate_limit.rs:48), so Default was the only way to build the type — a consumer could not supply a small table with a low frequency/max_size and was forced to couple its tests to upstream Chia's exact numbers.

impl From<&RateLimits> for OpcodeRateLimits

from_chia is moved into that impl, not duplicated. Default now reads Self::from(&*V2_RATE_LIMITS) and is byte-identical to before.

Deliberately NOT added: public fields and a field-wise constructor. The value of this type is that its numbers are derived from Chia's table rather than being a second set that can drift; a caller now chooses the source table, never an individual limit.

RateLimit, RateLimits and V2_RATE_LIMITS were already re-exported from the crate root (src/lib.rs:67), so a consumer can build a table without depending on chia-sdk-client independently — which is what keeps the module header's lockstep pin enforceable. The new impl's doc-comment states that a table keyed by a different chia_protocol version's ProtocolMessageTypes re-keys to shifted wire bytes and silently loosens every Chia opcode to default_settings — the exact hazard that note describes.

Blast radius checked

from_chia had exactly ONE caller: impl Default, same file, line 69. OpcodeRateLimits is constructed at src/link.rs:245 (::default()) and in this module's tests; it is exported at src/lib.rs:125 and named in SPEC.md:278. No consumer names from_chia (it was private). Risk: LOW — additive, one intra-file call site rerouted. Blast radius established by ripgrep over the worktree plus a read of every call site; the gitnexus index was not used for this crate (§2.0 fallback, stated).

Tests (TDD)

Both written first.

  • a_caller_supplied_table_governs_the_limiter — clones V2_RATE_LIMITS, retunes Handshake to frequency = 2.0, and asserts Admitted, Admitted, Deferred. RED evidence: it did not compile before the impl existed (E0308 at src/rate_limit.rs:299, the reflexive From<Self> being the only candidate) — which is precisely the point, the property was not expressible under a Default-only surface.
  • default_still_derives_from_the_upstream_table — the regression guard for the delegation. Probes with the message the custom table classifies differently (a third handshake; upstream's Handshake frequency is 5) and asserts Default and from(&*V2_RATE_LIMITS) agree AND that the probe is discriminating.

Fixture design: the payload is 16 bytes so no size cap can bind and the only budget that can refuse is frequency — the axis the custom table moves. The assertion is Deferred, not merely "not admitted", so an Unsendable from a mis-sized fixture would fail rather than pass for the wrong reason. The verdict is observed through admit, so the test proves what a consumer can actually do rather than reading private fields.

Load-bearing proof (committed first, mutation reverted after): rerouting Default to a table with Handshake at frequency = 1.0 makes default_still_derives_from_the_upstream_table FAIL while a_caller_supplied_table_governs_the_limiter stays green — the two tests are independent and the guard bites on exactly the defect it names.

cargo test: 70 + 9 + 5 + 4 + 2 + 1 pass, 0 fail. cargo fmt --check and cargo clippy --all-targets -D warnings clean.

Version

0.4.00.5.0. Additive, but a 0.x MINOR is semver-incompatible, so consumers must re-pin — expected and intended. SPEC.md §7.4 and conformance row C13 updated in the same commit; CHANGELOG.md left to git-cliff.

MichaelTaylor3d and others added 3 commits August 10, 2026 21:08
Co-Authored-By: Claude <noreply@anthropic.com>
The re-key was private, so Default was the only way to build the type and a
consumer could not exercise it against a cheap table -- any test was forced to
couple itself to upstream chia's exact numbers.

Exposing the conversion rather than the fields keeps the numbers DERIVED: a
caller picks the source table, never an individual limit, so the second set of
drifting numbers this type exists to prevent stays prevented.

Co-Authored-By: Claude <noreply@anthropic.com>

@MichaelTaylor3d MichaelTaylor3d left a comment

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.

PASS (correctness gate) — head e4f180c696d972c2b9a0d12370b6a5acb2a6eede.

Verified independently, in a throwaway worktree (no shared checkout mutated):

  1. Default unchanged — falsifiability PROVEN, not assumed. I rerouted Default to a table with Handshake at frequency = 1.0 and re-ran: default_still_derives_from_the_upstream_table FAILS (left: [Admitted, Deferred, Deferred] vs right: [Admitted, Admitted, Admitted]) while a_caller_supplied_table_governs_the_limiter stays green. The second assertion (via_default[2] == Admitted) is the anti-vacuity guard that stops the probe degenerating into Default-vs-Default. Mutation reverted; worktree clean.
  2. Re-key exists exactly oncefrom_chia is gone crate-wide; the body lives only in impl From<&RateLimits> (src/rate_limit.rs:59).
  3. No fields made public, no field-wise constructorOpcodeRateLimits' five fields remain private (src/rate_limit.rs:38-44); the only public constructors are Default and the new From. Derivation preserved.
  4. &*V2_RATE_LIMITS correctly derefs the Lazy; From takes a borrow.
  5. Footgun documented — the impl doc-comment names the cross-version re-key hazard explicitly (shifted wire bytes → silent fall-through to default_settings), and points at crate::RateLimits, which src/lib.rs:67-70 does re-export alongside V2_RATE_LIMITS, and ProtocolMessageTypes at src/lib.rs:61. The advice is actionable.
  6. Additive only — nothing removed or resignatured; 0.4.0 → 0.5.0 is the right call for a semver-incompatible 0.x minor.
  7. Gatescargo test --lib 70/70 locally; all 8 required checks green incl. Coverage (>=80% lines). Zero review threads open.

Non-gating nit, resolved by me, no action needed: SPEC.md:366 writes Default` is defined as `From<&V2_RATE_LIMITS>V2_RATE_LIMITS is a value, not a type, so the notation is loose; From<&RateLimits> applied to V2_RATE_LIMITS is what is meant. Not worth a round-trip.

Nothing handed to @copilot. Orchestrator owns undraft + merge.

@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review August 11, 2026 04:52
@MichaelTaylor3d
MichaelTaylor3d merged commit 17630c1 into main Aug 11, 2026
9 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the feat/rate-limit-from-chia branch August 11, 2026 04:53
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.

1 participant