Repository navigation
release: 0.5.0, Rule 28: the explorer calls deleted where the wallet's storage or the header service answers, the fallback shape where a call is irreducible; the toolbox 0.5.0 - #10
Merged
Conversation
…r without one The toolbox 0.5.0 takes the tip height from the header service and asks no explorer (its Rule 28 item 1). test_get_height asked a live explorer through the default Services; it now stands a local header service in front of the wallet and expects that service's tip, and a second test expects no height when no header service is configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… own storage
export-beefs asked WhatsOnChain for /tx/{txid}/beef once per transaction
holding an unspent output: one explorer request per output, for proofs the
wallet already holds. It now reads the outputs with their transactions
(list_outputs, include: entire transactions), which is the same walk over
the stored transactions and proofs that builds an outgoing BEEF, and cuts
one BEEF per txid from it. A transaction storage cannot carry back to a
proof is counted as failed and named; nothing is fetched from a third
party. The command holds no HTTP client.
Witness: export_beefs_writes_each_beef_from_the_wallets_own_storage. Red
at the base's loop with a local fixture in the explorer's place (one
request to /tx/{txid}/beef, nothing written while the fixture answered
500); green with no explorer in the code path.
export_beefs_names_no_explorer_and_no_http_client reads the source.
src/test_support.rs: a local fixture server that counts requests, and a
hand-built transaction, for this and the following items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…explorer's count
probe_input_spend read WhatsOnChain's /tx/hash/{spender} and took its
confirmations field as 'the spend is mined'. That field decides
RELINQUISHED (cleanup-abandoned, reconcile-outputs, the daemon's sweep),
and an explorer's count is a word. The probe now asks the wallet's services
for the spender's merkle path: Services::get_merkle_path returns a path only
after its root matched the header service's header, and none when no header
service is configured. No proof (not mined yet, a courier fault, no header
service) is confirmed: false, and the coin stays as it is. The explorer
request is gone.
reconcile() takes the services; the daemon's sweep and the two commands
pass the wallet's own.
Witness: the_spenders_confirmation_is_a_proof_never_an_explorers_count. Red
at the base: confirmed: true on a fixture explorer's {"confirmations":5}
with no proof to be had. Green: false without a proof, true with one, zero
requests to /tx/hash/{spender}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n time past from the header service gift-inspect asked WhatsOnChain's /chain/info for blocks and mediantime. Both are header facts the header service already answers: the tip header's height, and the median of the times of the tip and its ten predecessors (the node's rule, cited from the sibling: bsv-script-lean docs/BLOCK-CONSENSUS.md section 2.2). chain_clock reads the tip header and the ten headers before it through the wallet's services and takes the median as the node does. With no header service the command stops with an error naming CHAINTRACKS_URL; no explorer's number stands in. Witness: the_tip_and_the_median_time_past_come_from_the_header_service. Red at the base's request with a local fixture in the explorer's place (one request to /chain/info, its numbers taken as the clock). Green: the height and the median from eleven headers, out of order; the source names no /chain/info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wn transaction
receive with no --vout asked WhatsOnChain's /tx/{txid} for a decoded copy
of the outputs and matched our address in it, then fetched the BEEF of the
same transaction. The BEEF carries the transaction's own bytes, so the
BEEF is fetched once and the output paying our deposit script is found in
it. The second explorer request and the three structs that decoded it are
gone. A transaction that pays us nothing is an error, as before.
brc29::deposit_script: the P2PKH script of the deposit address, tied to
the address by a test.
Witness: the_output_index_is_read_from_the_beefs_own_transaction. Red at
the base's two requests with a local fixture in the explorer's place (a
request to /tx/{txid}, and no answer when it failed). Green: one request,
the BEEF, and the index from its transaction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in sync.rs deleted, two explorers behind 'unspent'
An output's spend was asked two ways with two error rules. sync
--reconcile-spent held its own copy: any failure of WhatsOnChain's spent
route read as 'not spent', any 200 relinquished the output whether or not
the spender was mined, and a second request asked whether the parent
existed. probe_input_spend had the three answers but one explorer, and its
'unspent' was that one explorer's 'no spend known' plus the parent's
presence.
There is now one function, and sync asks it. Its shape:
1. Who spent it: WhatsOnChain's /tx/{txid}/{vout}/spent, the one explorer
route we hold that names a spender. A name is checked by a proof (item
2). A 404 or a fault is not an answer and falls through.
2. Is it unspent: the toolbox's is_utxo (bsv-wallet-toolbox-rs 0.5.0, its
Rule 28 item 5): WhatsOnChain and Bitails, a rotating start, a positive
from the one that gives it, a negative only from both. Unspent is the
outpoint in an unspent set. Two explorers agreeing it is not unspent
while no spender is named (a phantom parent reads this way) is Unknown.
The parent-existence request (C6 in sync.rs, C9 in the probe) is gone with
the rule it served: an outpoint in an unspent set has a parent. The script
the unspent set is asked with comes from our own rows
(stored_locking_script).
sync --reconcile-spent relinquishes only on a proven spend. Its report
changes: 'phantom_parent' is replaced by 'spent_unproven' and 'unknown'
(JSON and text).
Witnesses, red then green: one_explorers_no_spend_known_is_not_unspent
(red: Unspent on one explorer's 404 and the parent's 200),
a_fault_at_the_spender_lookup_falls_through_to_the_unspent_set (red:
Unknown on the first fault), sync_holds_no_copy_of_the_spend_probe (red:
both request lines in sync.rs), only_a_proven_spend_relinquishes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ting start, absence only from both
'Has the network seen a transaction we just sent' was asked of the
broadcaster and of one chain index, WhatsOnChain, always in the same
place. The verifier now holds two chain indexes, WhatsOnChain and Bitails
(GET /tx/{txid}, the route the toolbox's own status read uses), and asks
them as one question with one answer (ask_chain_indexes):
- the start rotates, once per probe round, shared by every clone;
- a positive from the index that gives it ends the question;
- absence is every index answering 404; an index that could not look
leaves the answer Unknown, whatever the other said.
The absence rule stays: Rejected is still the broadcaster's absence AND the
chain indexes' absence in the last round of the window, and the reconciler's
absence clock still runs on the same report. What changes is that one
explorer's 404 is no longer the chain's absence. No attempt count, delay,
timeout or probe bound moved; a transaction absent from both indexes costs
two index requests a round where it cost one.
Witnesses, red then green:
one_chain_indexs_absence_is_not_absence_while_the_other_could_not_look
(red: Rejected on one explorer's 404), the_chain_index_start_rotates (red:
4 requests to the first and 0 to the second), bitails_is_a_second_chain_index
(red: WhatsOnChain alone). Controls: both absent is Rejected; the second
index's positive stands after a negative or a fault.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…services; gift-claim broadcasts through the wallet's broadcasters
gift-claim and gift-inspect each held a private copy of the WhatsOnChain
base and an HTTP client, and fetched the deposit's bytes from
/tx/{txid}/hex themselves: one explorer, no check that the bytes were the
transaction asked for. gift-claim then posted the claim to WhatsOnChain's
/tx/raw, past the wallet's broadcasters and their memory.
commands/gift_chain.rs holds the two things the gift commands ask of the
chain:
- deposit_bytes: Services::get_raw_tx. The toolbox hashes the bytes to the
txid, a second explorer stands behind the first, and 'no explorer has
it' is an error apart from 'could not look'.
- broadcast_claim: Services::post_beef, the ladder the wallet's own sends
use, with a BEEF of the deposit (with its proof when the header service
checks one) and the claim. The provider that accepted is reported;
gift-claim's JSON 'broadcast' field names it (it was the constant
'whatsonchain').
Neither command names an explorer or holds an HTTP client.
Witness: the_gift_commands_hold_no_explorer_and_no_http_client. Red at the
base (gift_claim.rs names reqwest). Green, with
the_deposits_bytes_come_through_the_wallets_services,
the_claim_is_broadcast_through_the_wallets_broadcasters,
a_mined_deposit_carries_its_checked_proof and
a_refused_claim_is_an_error_naming_the_refusal against the toolbox's mock
services.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead, at the site and in --help sync asks an explorer which outputs pay our deposit address: an address scan, the one question that has no header, proof or own-index answer by construction. Its help read 'Scan WhatsOnChain for unspent UTXOs at our deposit address and internalize new ones', as if it were the way to receive. - sync --help now opens 'Break-glass chain scan', says what it is kept for (finding a payment nobody handed us), that no daemon path runs it, that the routine path is fund with the BEEF the payer hands over, and that an empty list is not 'nothing was paid'. fund's help says it is the routine way to receive. - the request line carries the reason in the rule's words, and the command logs a warn line (marker break_glass_chain_scan) each time it runs. No behaviour of the scan changed: one explorer, an HTTP fault is an error, an empty list removes nothing. Witnesses, red then green: sync_help_says_it_is_a_break_glass_chain_scan_and_names_fund (red: the help lacked every one of the four words), the_address_scan_is_named_at_the_site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a second courier behind it
receive fetches a transaction's BEEF from WhatsOnChain's /tx/{txid}/beef:
one provider, and its fault was the command's error. The call is
irreducible (a payment to a bare address arrives with no BEEF from its
sender) and its answer is self-verifying (the BEEF is internalized: its
transaction hashes to the txid, its merkle paths meet the header service's
roots), so the explorer is a courier and is now named one at the site and
in receive --help.
Behind it stands a second courier, the toolbox's get_beef (the bytes from
either explorer, a merkle path only after its root met the header
service). WhatsOnChain stays first because its BEEF alone carries an
unmined transaction's ancestors. A fault at the first falls through (a
warn line, marker break_glass_beef_courier); when neither delivers the
error is 'could not look' with what each said.
Witness: a_courier_fault_falls_through_to_the_second_courier. Red at the
base (the fixture's 500 was the result). Green, with
the_second_courier_is_not_asked_while_the_first_delivers and
no_courier_delivering_is_could_not_look_with_both_reasons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st holds the list
Five explorer request lines remain in the CLI, each an irreducible
break-glass read: the two chain indexes of the broadcast verifier (C1:
WhatsOnChain /tx/hash/{txid}, Bitails /tx/{txid}), receive's BEEF courier
(C2: /tx/{txid}/beef), sync's address scan (C4: /address/{addr}/unspent)
and the spend probe's spender lookup (C7: /tx/{txid}/{vout}/spent). Each
now carries, directly above the request line, the reason no header, proof
or own-index answer exists, tagged 'Break-glass (Rule 28, C<n>)'.
tests/rule_28_sites.rs reads the source: every listed request line has its
reason within twelve lines above it; no file outside the list names an
explorer host; no unlisted file holds an HTTP client; no unlisted request
line exists. Red before the comments (the Bitails, courier and spender
lines had no tag at the site); green after. Comments and one reordered
log line only; no behaviour changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rule 28: an explorer is asked only where no header, no merkle proof and no row of the wallet's own answers the question, and then in one shape: two providers where a second exists, a rotating start, a negative only when both agree, and "could not look" kept apart from "nothing there". The CLI held 13 explorer request lines at 0.4.0; it holds 5, each naming its reason at the site (tests/rule_28_sites.rs holds the list). Needs bsv-wallet-toolbox-rs 0.5.0. Deleted (a header, a proof or our own storage already had the answer): 1. export-beefs builds each BEEF from the wallet's own storage; it asked an explorer once per unspent output. 2. A spender's confirmation is a merkle proof whose root the header service checked, never an explorer's confirmations count (cleanup-abandoned, reconcile-outputs, the daemon's sweep). 3. gift-inspect takes the tip height and the median time past from the header service. 4. receive reads the output index from the BEEF's own transaction. Kept, in the fallback shape: 5. One spend probe (three answers) for cleanup-abandoned, reconcile-outputs, the daemon's sweep and sync --reconcile-spent; the copy in sync is gone. "Unspent" is the toolbox's two-explorer read (WhatsOnChain and Bitails); one explorer's "no spend known" is not it. 6. The broadcast verifier asks two chain indexes, WhatsOnChain and Bitails, with a rotating start: present from either, absent only from both. The absence rule (the broadcaster AND the chain index, after the window) is unchanged. 7. gift-inspect and gift-claim read the deposit through Services::get_raw_tx; gift-claim broadcasts through the wallet's broadcasters. 8. sync says, at the site and in --help, that it is a chain scan and a break-glass read; the routine way to receive is fund with a BEEF from the payer. 9. receive's BEEF fetch is named a courier and has the toolbox's get_beef behind it. What an operator sees change: - CHAINTRACKS_URL was already required. With it set to off, getHeight is an error (it was an explorer's number), gift-inspect stops, and no spend is ever proven, so nothing is relinquished. - sync --reconcile-spent relinquishes only an output whose spender we hold a merkle proof of. Its report replaces "phantom_parent" with "spent_unproven" and "unknown" (JSON and text). - An input is restored as unspent only when an explorer lists it in an unspent set; a spent route's 404 alone no longer does it. With both explorers unreadable the input stays locked and is asked again. - A transaction is declared absent only when WhatsOnChain and Bitails both answer 404 (with the broadcaster's own absence). With one of them unreachable nothing is retired; it is kept and asked again. - gift-claim's JSON "broadcast" field names the broadcaster that accepted the claim (it was the constant "whatsonchain"). - export-beefs writes the BEEF storage builds (BEEF V2 hex); a transaction storage cannot carry back to a proof is counted as failed, not fetched. No limit changed: no attempt count, delay, timeout, page size or probe bound was added or moved. No stored data changes and there is no migration; 0.4.1 reads everything 0.5.0 writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The owner's Rule 28 of 2026-10-09 (bsv-stack-lean NORTH-STAR.md, Rulings), judged in docs/p0/rule-28-explorer-calls.md section 2 and executed by docs/p0/rule-28-cli.md, nine items each with a witness red then green: export-beefs builds each BEEF from the wallet's own storage (C10); the spender's confirmation is a proof, never an explorer's count (C8); the tip height and the median time past from the header service (C13); the output index from the BEEF's own transaction (C3); one spend probe with three answers and the toolbox's second provider (C5, C7); a second chain index beside WhatsOnChain with a rotating start (C1); the gift commands through the wallet's services and broadcasters (C11, C12); sync named as a chain scan and a break-glass read at the site and in --help (C4); the courier named with a second provider (C2). The toolbox at 0.5.0 from crates.io. No limit changed. Verified by the captain at the tip against the registry: 310 tests pass; clippy and fmt clean; the publish dry run packages 117 files. Four open questions for the owner are in the report.
🤖 Generated with Claude Code