fix: persist BEEF ancestry through the wallet lifecycle#473
Merged
Conversation
Transaction#to_ef now falls back to source_transaction.outputs[prev_tx_out_index] when source_satoshis or source_locking_script are not explicitly set on an input. Derivation is non-mutating — calling to_ef twice produces identical bytes and input attributes remain unchanged. Raises ArgumentError only when neither explicit fields nor a wired source_transaction are available. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…regression spec (#469) `store_proofs_from_beef` already calls `@storage.store_transaction` for every tx in the BEEF (including the subject), making the explicit call on the line below it redundant. Remove the duplicate and add a regression spec with 9 examples covering 3-generation ancestry, TXID_ONLY entries, and idempotency to lock in the persistence guarantee. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CHANGELOG entry under Unreleased covers the to_ef source-data derivation fix (#467) and the from_beef find_atomic_transaction rewrite (#468), with consumer impact and cross-reference to wallet-toolbox#149. Transaction guide gains a "Extracting a transaction from BEEF" subsection showing the from_beef → to_ef_hex broadcast flow. Wallet guide notes the internalize → create → broadcast round-trip coverage added in Task 3/4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exercises the full internalize → create_action → broadcast lifecycle with a 2-generation BEEF (grandparent confirmed, parent raw-only). The mocked ARC calls to_ef_hex on the broadcast tx — raises if ancestry is not wired, proving both Task 1 (to_ef fallback) and Task 2 (from_beef wiring) are needed. Covers: happy path with full ancestry, unproven parent edge case, and a regression-defence scenario that strips ancestry to confirm the test fails without the fixes in place. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sgbett
added a commit
that referenced
this pull request
Apr 16, 2026
The previous spec invoked merge_bump after merge_transaction, which triggers F5.6's retroactive upgrade. The ancestor then landed on-wire as FORMAT_RAW_TX_AND_BUMP and the parser populated merkle_path at read time — before find_atomic_transaction ever ran. The spec passed but would still have passed with the pre-#468 from_beef implementation (bare subject extraction without find_atomic_transaction routing). Rewrite to bypass merge_bump: push the BUMP directly into beef.bumps so the ancestor stays FORMAT_RAW_TX on-wire. Add a sanity assertion that the serialised ancestor.format is FORMAT_RAW_TX, so a future change that reintroduces the F5.6 path silently would fail the spec. Verified the spec now fails when from_beef is reverted to the old `beef.transactions.reverse.find(&:transaction)&.transaction` shape. Ref: remote review on PR #473, bug_001_1. Co-Authored-By: Claude Opus 4.6 (1M context) <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.
Summary
Fixes the architectural gap that caused x402-rack phantom-payment failures and the same class of bug reported upstream in wallet-toolbox#149. Closes #466.
The real bug (after investigation)
Not what the HLR initially described. The storage side of
internalize_actionwas fine. The failures came from two places:Transaction#to_eftoo strict — required explicitsource_satoshis/source_locking_scripton every input, rather than deriving from wiredsource_transaction. This caused silent fallback to plain hex, which ARC rejects for tx with unconfirmed parents. Ruby was stricter than TS.Transaction.from_beefdidn't wire ancestry viafind_atomic_transaction— late-bound BUMPs onFORMAT_RAW_TXancestors weren't attached.Fix
to_efnow derives source data from wiredsource_transactionwhen unset (matches TSwriteEF). Non-mutating — source fields on input objects remain untouched.from_beefusesBeef#find_atomic_transactionso ancestry is wired recursively including late-bound BUMPs.internalize_actionpersists all ancestors (was already correct — spec cements the guarantee).store_transactioncall ininternalize_action(duplicated work done bystore_proofs_from_beef).to_ef_hexraises.Consumer impact
Consumers doing
Transaction.from_beef(bytes)thenARC#broadcast(tx)— or receiving a BEEF viainternalize_actionand later spending viacreate_action— now work correctly. Previously these silently failed or produced invalid EF.Test plan
to_effallback covered with EF-specific specsfrom_beefancestry wiring covered with new specsinternalize_actionancestor persistence covered with regression specsCloses #466, #467, #468, #469, #470, #471, #472
🤖 Generated with Claude Code