Skip to content

feat(wallet): draw corroboration peers with exclusion instead of dial-and-discard - #226

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
loop/2904-chia-query-0-6-2-adoption
Aug 14, 2026
Merged

feat(wallet): draw corroboration peers with exclusion instead of dial-and-discard#226
MichaelTaylor3d merged 3 commits into
mainfrom
loop/2904-chia-query-0-6-2-adoption

Conversation

@MichaelTaylor3d

@MichaelTaylor3dMichaelTaylor3d commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

DO NOT MERGE — gate round has not returned. dig-node's nightly-release.yml
stable job is guarded by event_name == 'schedule', so main publishes a real
stable release unattended at midnight UTC (#698). This stays DRAFT until a gate
verdict is posted.

Closes DIG-Network/dig_ecosystem#2904

The defect being removed

ChiaQuorumCorroborator::probe drew through chia_query::peer::connect::connect_random_peer,
which tries the priority addresses — TRUSTED_FULLNODE, then 127.0.0.1 — ahead of discovery on
every call. Un-excluded, a host with any co-resident process on 8444 was handed the same local
address on every attempt: a probe round burned its whole MAX_PROBE_ATTEMPTS budget re-drawing one
peer and returned Verdict::Insufficient, which writes nothing. Corroboration therefore failed
silently on exactly the machines normal users run.

The fix has existed in chia-query since 0.10.0 and was backported to the 0.26-generation line as
0.6.2. This PR is the consumer adoption — a contract crate is done only when its consumers
adopt it (CLAUDE.md §1.3b).

What changed

  • crates/dig-wallet/Cargo.toml: chia-query"0.6.1""0.6.2". dig-node-core's =0.5.1
    is deliberately left alone
    — the two-line lock is documented at crates/dig-wallet/Cargo.toml:66-72
    (#2227).
  • probe draws through connect_random_peer_excluding, passing the addresses the round already holds.
  • Only PeerOrigin::Discovered draws are admitted as opinions. A priority peer is preferred for
    speed; it is not an independent voice, because a co-resident process is precisely a source a local
    attacker can supply. Priority draws are ruled out of the round while still being excluded from
    re-drawing.
  • Sample assembly extracted as assemble_distinct_sample, with the dial as a seam — neither rule is
    observable through a real dial.
  • 0.119.0 (minor: probe behaviour changes); dig-wallet 0.23.0.

independent_peer_count() was deliberately NOT adopted. Its only candidate call site is
sage/chain.rs:157, whose peer_count is documented at sage/quorum.rs:195 as the transport's
held pool — "measured, not targeted". Swapping in the independent count would silently change a
documented surface's meaning and would edit a file this PR proves needs no edit. Logged as a
follow-up instead.

Blast radius checked

Via ripgrep + direct read (§2.0 fallback bound 2 — a full gitnexus analyze dirties tracked files,
#2910; stated rather than hidden):

  • ChiaQuorumCorroborator::probeexactly one caller, the Corroborator impl at
    sync_supervisor.rs:2090. Nothing outside this file calls it.
  • ChiaQuorumCorroborator is constructed in two places (sage/service.rs:241, tests.rs:1869),
    both via ::mainnet(), whose signature is unchanged.
  • assemble_distinct_sample + Draw are new and private to this module.
  • One connect_random_peer call remains, at sync_supervisor.rs:1865 in ChiaPeerSessionFactory.
    It is correct un-excluded: it wants ONE session peer and counts no opinions, so "try the local node
    first" is the point there. Its stale doc line is corrected in this PR.
  • §908 / custody: no config.trusted is set anywhere on this path. The discovery session keeps
    trust_for(DialSource::Discovery). Priority orders discovery; it never confers authority.

Risk: contained/LOW — one private function, one caller, no public signature changed.

How verified

  • cargo check -p dig-wallet --locked clean with zero source edits on the pin bump alone — this
    is the load-bearing verification that 0.6.2 is the same chia generation as 0.6.1. The earlier
    attempt's 7-file blast radius was an artifact of the abandoned 0.10.0 generation jump, not of this
    fix. The six other chia_query files (chain.rs, fallback.rs, service.rs, spend.rs,
    tipping.rs, sync_supervisor/tests.rs) are untouched, so there is no collision with PR fix(wallet): scope the chain-fallback CAT read to its asset id #224.
  • cargo test -p dig-wallet --locked: 549 + 22 passed, 0 failed.
  • cargo clippy -p dig-wallet --all-targets --locked: clean. cargo fmt applied.
  • cargo metadata --locked exit 0.

Each new test proven load-bearing by mutation

All three assert on the set of addresses reached, never a count or a success flag, and the
co-resident dialler counts its calls so a round that finished quickly is distinguishable from one
that made progress.

MutationResult
baseline3 passed
dial(ruled_out.clone())dial(Vec::new()) (exclusions never handed to the dialler)co-resident + priority tests FAIL
PeerOrigin::Discovered filter → if falseco-resident + priority tests FAIL
defensive ruled_out.contains(&addr) re-admission guard → if falseexclusion-ignoring-dialler test FAILS (uniquely)

The third mutation isolates the third test, so the defensive guard is not redundant with the
exclusion ask — it is what keeps a dialler that ignores its exclusions from turning one opinion into
a "quorum".

Note on the reused work

The parked branch loop/2904-chia-query-0-10-adoption @ 305cea1 (closed PR #225) was cherry-picked
rather than rewritten. Its tests did not compile (E0373/E0507 — the Fn seam needs
async move plus a reborrowed dialler), confirming they had never been gated. Fixed here.

@MichaelTaylor3d

Copy link
Copy Markdown
ContributorAuthor

CORRECTNESS GATE: PASS

Head reviewed: 6f1100c3caa55f094058aace5cfb61fa8ea6ab46 (resolved from the remote; matches the worktree tip). Draft, DO NOT MERGE first line — stays draft. Phase-1 posture (CLAUDE.md §2.6): one correctness pass, off-path findings logged below and not gating.

All six questions answered in favour of the change; all three mutation claims reproduced and confirmed. No money, custody, or privileged-action surface is made to lie. §908 untouched — nothing on this path signs.

The six questions

1. Does excluding PeerOrigin::Priority trade a wrong verdict for NO verdict? Only in a narrow case, and the trade is right and fail-closed. CORROBORATION_FLOOR = 2 (crates/dig-wallet/src/sage/quorum.rs:172), so the pre-fix code on a host whose only reachable peer was the co-resident node already yielded Insufficient (distinctness rejected the re-draws, giving a sample of 1 < 2). The genuinely new loss is a host with two or more distinct reachable priority addresses (a configured TRUSTED_FULLNODE plus loopback) and zero discovery: pre-fix that could reach a 2-answer verdict, post-fix it yields Insufficient. Counting an operator-configured node and a co-resident process as two independent voices is exactly the sample-of-one hazard the module exists to prevent, so refusing is correct. Crucially it is not permanent denial: Insufficient becomes SessionTrust::refused(RefusalReason::Undecided) (sync_supervisor.rs:1495-1507), which accuses no one, writes nothing, and re-corroborates after RECORROBORATE_AFTER. No durable exclusionary record is written from it.

2. Mutation claims — reproduced, all three, in the warm worktree. Baseline: 3/3 green.

  • Remove if ruled_out.contains(&draw.addr) { continue; } -> a_dialler_that_ignores_exclusions_exhausts_the_bound_without_hanging FAILS uniquely (left: 10, right: 1); the other two pass. The defensive guard is not redundant with the exclusion ask, and it is pinned.
  • Remove the PeerOrigin::Discovered filter -> the co-resident and priority-peer tests both FAIL; test 3 passes.
  • Replace dial(ruled_out.clone()) with dial(Vec::new()) -> the same two FAIL; test 3 passes.

Each of the three behaviours therefore has an independent killer.

3. Is assemble_distinct_sample a genuine seam? Yes, and it orphans nothing that was previously covered. The extracted function holds the two rules (exclusion feed-back, origin filter); the production closure in probe is a thin connect_random_peer_excluding(...).ok()? then await_peak(...)? then Draw. That closure is untestable without a live full node, and it was equally untested before the refactor, when the same loop was inline. Coverage of the interesting logic went up, not sideways; no verification-must-reach-production-code regression. Residual gap logged below.

4. The independent_peer_count() non-adoption is correctly judged.crates/dig-wallet/src/sage/chain.rs:157 fills ChainPeerTier::peer_count from client.peer_count(), and quorum.rs:194-196 documents that figure normatively: the peer count a user sees (chia_peer_count) is the transport's HELD pool and is "measured, not targeted — a node holding three peers reports three however wide this is set." Swapping in an independence-filtered count would make a node holding three peers report fewer, i.e. silently redefine a documented user-visible surface. Declining it is right; the one unfulfilled brief item is unfulfilled for a good reason.

5. sync_supervisor.rs:1865 is correctly left on the un-excluded connect_random_peer. Verified in situ: ChiaPeerSessionFactory::connect takes ONE session and tallies no opinions, and the peer it returns is labelled trust_for(DialSource::Discovery) giving PeerTrust::Discovered, which trust_for_session (:1483-1494) routes through corroboration rather than elevating. So a localhost draw there is fail-closed: it earns no write authority without a corroborated round. Only the stale doc line changed. Not a missed call site.

6. Test non-vacuity beyond the mutations. The tests assert on the ordered set of addresses reached, plus an explicit !reached.contains(&local_addr()). On the pre-fix shape (un-excluded dial) the co-resident dialler returns loopback on every attempt, so reached is either the single loopback address or empty — either way assert_eq!(reached, expected) fails. The attempt-bound concern is handled properly: MAX_PROBE_ATTEMPTS = QUORUM_DIAL_WIDE * 2 = 20, and the co-resident test pins attempts == target + 1 == 11exactly, distinguishing "terminated because it succeeded" (11) from "terminated because it gave up" (20). Test 3 pins the opposite end at == MAX_PROBE_ATTEMPTS. Both directions are stated.

Also confirmed

  • dig-node-core's chia-query = "=0.5.1" deliberately not aligned (rationale at crates/dig-node-core/Cargo.toml:87-105 and crates/dig-wallet/Cargo.toml:66-72, #2227); Cargo.lock carries both lines on purpose (:833 = 0.5.1, :859 = 0.6.2).
  • Version bumps complete: root version = "0.119.0", the sole version.workspace = true member (dig-node-service), and dig-wallet 0.23.0 all present in the lock. No missing lock entry.
  • cargo metadata --locked exits 0.
  • No config.trusted is set anywhere on this path. Priority ordering affects discovery only; PeerTrust/WriteAuthority still gates custody, and a priority draw is now ruled out of the tally rather than elevated.
  • Upstream semantics match the adoption: chia-query 0.6.2 src/peer/connect.rs:118-190 filters exclude out of both the priority list and the discovery list, returning PeerOrigin::Priority for the former and Discovered for the latter.
  • §2.5: doc comments carry WHY, names are intent-revealing, Draw<T> is the right small abstraction, guard-clause style throughout. Passes.

Off-path findings — LOGGED, NOT GATING (§2.6 phase 1)

  1. Nothing tests that the production dial closure actually forwards &exclude to connect_random_peer_excluding, or that the argument order is right — a mutation there (rather than in the seam) would go green. Inherent to a real-network function; an injected dial trait would close it during a hardening pass.
  2. A round on a host with a live priority peer now spends one extra dial per round ruling it out. Bounded and cheap (1 of 20), but the cost is per-round and permanent.
  3. Diagnostic silence: a failed dial is .ok()?-discarded with no tracing event, so a round that ends Insufficient because discovery kept failing is indistinguishable in the logs from one that never dialled. Pre-existing shape, unchanged here.
  4. Pre-existing, outside the diff: the sync_supervisor.rs:1505 log message contains a long run of spaces mid-sentence (the cargo fmt string-continuation corruption). Cosmetic, inside a debug!.

Required checks

Green at review time: Rustfmt, Clippy, Release-script tests, Commitlint (both), Check version increment, CodeQL (actions / javascript-typescript / rust). Still IN_PROGRESS: Test + coverage and the four Native install packages builds. This PASS is a correctness verdict only; the §2.4a precondition still requires check-merge-preconditions.sh to exit 0 by name before any merge, and the PR stays draft until its gate round closes.

Disclosure

Mutation probing was performed in the lane's own worktreeC:/tmp/worktrees/dig-node-2904 (as directed, to reuse the warm target/), never in the primary checkout. Method: copy the file aside, apply one mutation, run the three tests, restore by copying the backup back — three mutations applied and reverted in sequence. No git checkout, reset or stash was used. Final state verified: git status --porcelainempty, HEAD still 6f1100c3, no stash, no local commits, no PR-only residue. The primary modules/apps/dig-node checkout was not touched; every dependency fact above was read from the worktree at this head.

MichaelTaylor3dand others added 3 commits August 14, 2026 04:35
WIP toward #2904. The pool fix landed on the 0.26-generation line as
0.6.2, so this is a patch bump within one chia generation.
Co-Authored-By: Claude <noreply@anthropic.com>
…iscard
ChiaQuorumCorroborator::probe drew through connect_random_peer, which tries
the priority addresses -- TRUSTED_FULLNODE, then 127.0.0.1 -- ahead of
discovery on EVERY call. On a host with any co-resident process on 8444 the
round was handed the same local address on every attempt, burned its whole
MAX_PROBE_ATTEMPTS budget re-drawing one peer, and returned
Verdict::Insufficient -- so corroboration silently failed on exactly the
machines normal users run.
The round now draws through connect_random_peer_excluding, passing the
addresses it already holds, and admits PeerOrigin::Discovered draws only: a
priority peer is preferred for speed, never counted as an independent voice.
Sample assembly is extracted as assemble_distinct_sample with the dial as a
seam, because neither rule is observable through a real dial.
Refs #2904
Co-Authored-By: Claude <noreply@anthropic.com>
Minor: probe behaviour changes -- priority draws are no longer counted as
independent voices. dig-wallet 0.22.0 -> 0.23.0 for the same reason.
Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3dforce-pushed the loop/2904-chia-query-0-6-2-adoption branch from 6f1100c to bb43d0dCompareAugust 14, 2026 11:37
@MichaelTaylor3d

Copy link
Copy Markdown
ContributorAuthor

Rebased onto main by the orchestrator: 6f1100c3bb43d0d7. Now ahead 3 / behind 0; branch protection has strict=true, so this was required before merge.

The gate PASS on 6f1100c3 (comment 5292800631) carries forward, and I verified that rather than assuming it. Diffing the files the gate actually reviewed —crates/dig-wallet/src/sage/sync_supervisor.rs, sync_supervisor/tests.rs and crates/dig-wallet/Cargo.toml — across 6f1100c3..bb43d0d7 returns empty: byte-identical. The whole delta is the new base arriving (PR #224's rpc.rs, fallback.rs, SPEC.md, CHANGELOG.md). So no re-gate; the reviewed code has not moved.

The one conflict was the predicted one, on the version-bump commit: root Cargo.toml and Cargo.lock each carried a single 0.118.1 (main, from #224) vs 0.119.0 (this branch). Resolved to 0.119.0, which stays ahead of the released 0.118.1. Verified after resolving:

  • Zero conflict markers anywhere in the tree — git rebase --continue will happily commit a file still containing them, so this was grepped, not assumed.
  • No stale 0.118.1 left in Cargo.toml or Cargo.lock.
  • cargo metadata --locked exits 0 — the authoritative check that the lock agrees with every manifest, including the one version.workspace = true member (crates/dig-node-service).

Note for whoever merges: I rebased before the two in-flight checks (Test + coverage, build .msi) finished, deliberately — a rebase invalidates them anyway, so this way they run once against the final head instead of twice.

Still DRAFT, as it should be until the checks return. dig-node's cron cuts a real stable release from main unattended at 00:00Z (#698), so the merge gate here is the last line of defence, not the first of two.

Context: v0.118.1 is being released now (run 31796733627), carrying #224's money-lie fix. This PR is v0.119.0 on top of it.

@MichaelTaylor3d

Copy link
Copy Markdown
ContributorAuthor

PRE-MERGE SECURITY GATE — PASS

Head audited: bb43d0d746871c6e90ef74eab8e8198de61989c6 (resolved from gh pr view 226 --json headRefOid; merge-base 3faf53a6021d0e30b89e10f9198264ba1f4c5caf). Security leg only — the correctness leg passed separately at 6f1100c3 and was not re-derived.

Diff audited in full: Cargo.toml, Cargo.lock, crates/dig-wallet/Cargo.toml, crates/dig-wallet/src/sage/sync_supervisor.rs, crates/dig-wallet/src/sage/sync_supervisor/tests.rs, plus the entire chia-query 0.6.1 to 0.6.2 source delta.

No shared checkout was mutated. All reads were git show / git diff against objects; the working tree's pre-existing uncommitted crates/dig-wallet/src/sage/rpc.rs was left untouched. No worktree was cut — every finding is decidable from source.


The four questions

1. Can an adversary influence which peers end up in the corroboration set? — No, and the change strictly REMOVES an attacker capability.

The exclusion set is ruled_out (sync_supervisor.rs:2032), populated only from draw.addr (:2047) — the socket address this client dialled, taken from chia-query's own dial bookkeeping. A remote peer cannot cause the client to record an address it did not dial, and cannot cause an honest peer's address to enter the exclusion set. exclude never leaves the process, so there is no information leak either.

The capability actually removed: under chia-query 0.6.1 the priority addresses (TRUSTED_FULLNODE, then 127.0.0.1:8444) were re-tried on every call un-excluded, so a co-resident process that binds 8444 was returned on all MAX_PROBE_ATTEMPTS dials. Under 0.6.2 plus this change it is drawn once, ruled out, and never offered again — and it is not tallied at all.

2. Does the quorum still require genuine independence? — Yes, and independence is tightened.

sync_supervisor.rs:2048 admits PeerOrigin::Discovered draws only. A priority-origin peer is ruled out and not counted (:2047 runs before the origin check at :2048, so a rejected priority peer is not re-offered either). That is the correct side of the custody line: priority orders discovery, it does not confer authority — the preferred/trusted flag stays a discovery-ordering hint here and grants nothing. PeerTrust, WriteAuthority and may_elevate are untouched by this diff (verified: the only PeerTrust token anywhere in the diff is inside a doc comment).

Downstream independence bounds are unchanged: band_kept_a_majority still demands the credibility band keep a strict majority of claimants (6-of-10 at a full round), and required_agreement(answered) = max(ceil(answered*3/4), 2) — so a thinner sample is not a cheaper one (2 answers require 2-of-2 unanimity). Nothing in the diff lowers either bar. The claimant denominator now excludes priority peers, which can only make an attacker's required fraction the same or harder, never easier.

3. Does rotation survive? — Yes.

ruled_out is a local Vec constructed fresh inside assemble_distinct_sample (:2032), called once per probe(), called once per corroborate() (:2091). ChiaQuorumCorroborator holds exactly two fields, network and timeout (:1925-1928), so there is no cross-round state that could pin a set. SESSION_MAX_LIFETIME (600s) rotation and every refusal path both return through the reconnect path, which re-runs corroborate() with an empty exclusion set. NC-12's rotation half is intact; its agreement half is the unchanged quorum module.

4. Failure direction — closed, on every path.

  • create_generated_tls() fails: empty sample.
  • A dial errors, or the peak never arrives: continue, attempt burned.
  • Empty or thin claimant set: hold_best returns None (band_kept_a_majority(0, 0) is 0 > 0, false) and corroborate returns Err(SyncError::Peer) at :2107-2121.
  • No settled height: Err.
  • Below CORROBORATION_FLOOR, or split: Verdict::corroborated() is None, may_elevate is false, SessionTrust::refused, PeerTrust::Discovered — which writes nothing.

No path in this diff produces a written peak from a smaller or attacker-chosen set. The money-lie class (a wrong balance reported as settled) is not reachable through the change: the failure mode is "the replica is not written", i.e. the honest-unknown state.

Other areas checked

  • Reachability — who can invoke this.corroborate() has exactly one production caller, trust_for_session (:1502), reached only from the supervisor loop spawned once at service start (service.rs:231). No RPC handler, no peer-reachable entry point; an anonymous caller cannot trigger a probe round.
  • Amplification. Worst case per round is 20 dials, unchanged from base. A hostile writer peer can force session churn (pre-existing, #2868), but each churn is rate-limited by the probe's own duration and the dial targets come from DNS introducers, not from the attacker — not a usable amplification primitive, and not introduced here.
  • Persisted state. None added. ruled_out is in-memory and bounded at 20 entries by the attempt loop; the per-iteration clone() is negligible.
  • Panics / bounds. No indexing, no unwrap, no arithmetic over adversarial input in the new code; let Ok(..) else / let Some(..) else throughout.
  • Dependency. chia-query 0.6.1 to 0.6.2 is a DIG-Network crate on a maintained backport line (main is 0.10.0 — version order is not publish order here). Lock checksum f0edb60efb90cf0cf3238773489049828b85cb0f7d5e82b8ee1ec6b72084b3d6 verified by sha256sum against the .crate in the local registry cache. The source delta is confined to lib.rs, peer/connect.rs, peer/mod.rs, peer/pool.rs and tests; I read all of it. Zero new package entries in Cargo.lock (10 changed lines total, all version/checksum). The new PeerPool::admit decides distinctness and capacity under the write lock, closing a real TOCTOU in the concurrent initial fill — a tightening, not a loosening.
  • Test integrity. The three new tests are non-vacuous. a_co_resident_node_no_longer_consumes_the_probe_budget asserts on the set of addresses reached and pins the attempt count at target + 1, so a lucky re-draw cannot pass. priority_peers_are_never_counted_as_independent_voices puts two priority sources in front of two discovered ones, so the assertion is on contents rather than size. a_dialler_that_ignores_exclusions_exhausts_the_bound_without_hanging pins termination at the bound. chia-query's own the_local_node_is_offered_when_it_is_not_already_held is the control that would catch the tempting "just delete the prepend" mis-fix.

Log-and-defer (NOT blocking — pre-existing, non-regressive, fails closed)

D1 — the exclusion only records a COMPLETED draw, so a silent co-resident peer is still re-offered on every attempt.

sync_supervisor.rs:1977let claim = await_peak(receiver, self.timeout).await?; — returns None out of the dial closure beforeDraw is constructed, so the address is lost. sync_supervisor.rs:2039let Some(draw) = dial(ruled_out.clone()).await else { continue; }; — then continues without recording anything.

Scenario: an unprivileged co-resident process binds 127.0.0.1:8444, completes the chia handshake, and never sends NewPeakWallet. chia-query tries the priority addresses first on every call, the address is never excluded, so all 20 attempts hit it, each costing DIAL_TIMEOUT (10s) — a ~200s round yielding an empty sample, then Err, then RECORROBORATE_AFTER, forever. This is the guard-specificity pattern: the change's rationale names a co-resident process that answers ("is handed the same local address as many times as it asks"), and the one-off variant that answers with nothing walks past it.

Why it does not gate: verified identical in the base. At 3faf53a6 the duplicate check was sample.iter().any(...) against admitted peers only, so a silent localhost was likewise never recorded and await_peak timed out on all 20 attempts — same 200s, same empty sample, no regression. And the outcome is denial of sync (replica unwritten, initial_sync_complete stays false), not a wrong balance reported as settled, so it sits outside the one class CLAUDE.md 2.6 still stops a merge for. Suggested follow-up: record addr in ruled_out on any completed connection, whether or not a peak arrived.

D2 — a round that reached NOBODY is logged as a credibility-band split.

sync_supervisor.rs:2107-2121: with an empty candidates, eligible returns empty and band_kept_a_majority(0, 0) is false, so the None arm fires and emits "the credibility band excluded half or more of the peers that claimed a peak" with claimants = 0. Diagnostics only — the write gate is correct either way — but it points an operator at a coordinated-peer-group hypothesis when the truth is "discovery reached nothing". Pre-existing. Cheap fix: distinguish candidates.is_empty() before consulting the band.

Neither is on the money/custody path; per 2.6 both are logged, not gated.

Not covered

  • The balance/confirmation surfaces downstream of the corroborated peak (stale-vs-fresh labelling) — outside this diff, covered by prior gates.
  • Runtime execution: no probe was run against a live network and no worktree was cut. Every finding is decided from source plus the byte-verified dependency delta; the base-vs-head equivalence behind D1 was established by reading both revisions, not by execution.
  • Correctness, by instruction — that leg passed separately.

Verdict: PASS. No security defect introduced by this diff. Peer selection under the new path is at least as independent as before, and strictly more so on a host with a co-resident node; rotation is preserved; every failure direction is closed.

@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review August 14, 2026 15:47
@MichaelTaylor3d
MichaelTaylor3d merged commit c4c84f4 into mainAug 14, 2026
15 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the loop/2904-chia-query-0-6-2-adoption branch August 14, 2026 15:48
Sign up for freeto 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

@MichaelTaylor3d