fix(sdk): parse AtomicBEEF in hex_to_bsv_tx to preserve source transactions#103
Open
E-Jacko wants to merge 1 commit into
Open
fix(sdk): parse AtomicBEEF in hex_to_bsv_tx to preserve source transactions#103E-Jacko wants to merge 1 commit into
E-Jacko wants to merge 1 commit into
Conversation
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
Detects AtomicBEEF / BEEF magic bytes at the start of the hex stream in
hex_to_bsv_txand parses viaBeef::from_hexso that wallet-toolbox'sCreateActionResult.txfield — which is AtomicBEEF binary (bsv-blockchain/ts-stack/packages/wallet/wallet-toolbox/src/signer/methods/createAction.ts:66:r.tx = beef.toBinaryAtomic(r.txid)) — is unwrapped withsource_transactionpopulated on every input.Without this fix, the SDK treats AtomicBEEF bytes as raw tx hex, the parser runs out of buffer mid-parse, and the broadcast loop downstream fetches every input's parent over HTTP individually — even when the parent was just produced by the wallet in the same session.
Problem
Upstream HEAD
packages/runar-rs/src/sdk/contract.rs:23-25:BsvTransaction::from_hexparses ONLY the raw transaction wire format. When called with the hex of an AtomicBEEF envelope (prefix01010101+ 32-byte txid + BEEF V1/V2 body + per-tx framing + merklePath chain), the inner reader walks into the BEEF framing bytes, mis-interprets them as tx fields, and either errors withI/O error: failed to fill whole bufferor returns a malformedTransactionwith nosource_transactionon any input.This isn't a corner case — it's the hot path. Every successful
wallet_toolbox.create_action(...)call returnsresult.txas AtomicBEEF (TS canonical verified atcreateAction.ts:62-67andsignAction.ts:35). Treating those bytes as raw tx hex either fails outright or strips the parent context, forcing every downstream broadcaster to re-fetch parents over HTTP for inputs the wallet itself just supplied.How this surfaced
Surfaced while integrating wallet-toolbox-returned AtomicBEEF into the SDK's broadcast pipeline. A
result.txfield returned fromcreate_actionwas passed throughhex_to_bsv_tx(via the SDK's standard deploy/call path), the parser failed mid-way through the AtomicBEEF prefix, and the downstream broadcast loop saw every input withsource_transaction = None. The broadcaster then tried to fetch each parent via overlay/HTTP — including parents the wallet had created moments earlier — to reconstruct the EF/BEEF envelope it needs to send to ARC. Both the parse failure (when the raw-tx parser errors) and the parent-fetch storm (when it returns a malformed but non-erroring tx) were visible in production.Fix
Helper changed from
fn(private) topub fnso the regression test can exercise it through public surface. No production-side semantic change from thepub.The magic-byte discriminator is unambiguous: raw Bitcoin transactions always start with a 4-byte little-endian version field (
01000000for v1,02000000for v2). None of those collide with AtomicBEEF (01010101) or BEEF V1/V2 (efbe0001/efbe0002). Raw-tx hex input continues through the legacyBsvTransaction::from_hexpath unchanged.Cross-language parity
This is the strongest port-parity story in this batch. TS canonical (
bsv-blockchain/ts-stack/packages/sdk/src/transaction/Transaction.ts:121):…is the canonical entry-point for unwrapping the same byte layout. The TS SDK's
Transaction.fromAtomicBEEF()walks the same prefix + BEEF body and returns aTransactionwithsourceTransactionpopulated on every input — exactly the shape this PR produces in Rust.The wallet-toolbox client class uses this canonical pattern at
CWIStyleWalletManager.ts:451-456:i.e., the canonical sequence is:
createAction→r.tx(AtomicBEEF bytes) →Transaction.fromAtomicBEEF(r.tx)→ broadcaster. This PR makes the Rust SDK'shex_to_bsv_txcapable of the same unwrap, so the Rust broadcaster sees fully-populated inputs.Verification
Passes with patch:
Fails without patch (surgical — strip the BEEF-detection block, keep
pub fn):Pre-patch,
hex_to_bsv_txtries to parse the AtomicBEEF envelope bytes as a raw tx — the wire layout doesn't conform, the inner reader runs out of buffer mid-parse, and the test's assertion that the inner Transaction'sinputs[0].source_transactionisSome(...)cannot even be reached. The raw-tx fallback test still passes — locking the no-regression property for non-BEEF input.The bundled test uses only upstream-existing deps (
bsv-sdk 0.1.72, the same version pinned in upstream'sCargo.lock). It constructs a parent tx P, a child tx C that spends P, wraps them in an AtomicBEEF envelope with merkle path for P, hex-encodes the envelope, and assertshex_to_bsv_tx(hex)returns C withC.inputs[0].source_transaction == Some(P).Backwards compatibility
01000000or02000000) continues through the legacyBsvTransaction::from_hexpath with byte-identical behaviour. Locked byr_ab_hex_to_bsv_tx_falls_back_to_raw_tx_when_no_beef_magic.fntopub fn(additive — no existing call site is forced to import it).Beefis already imported viabsv::transaction::beef(already a dep ofrunar-rs).Related
runar broadcast and get_transaction: with the AtomicBEEF unwrap restoringsource_transactionon every wallet-supplied input, the broadcast loop downstream no longer needs to fetch those parents over HTTP. R-AB + R2 (parse cached bytes) together close the "every input arrives withsource_transaction = None" gap.docs(wallet): document AtomicBEEF format on CreateActionResult.tx: the docs PR against the TS SDK that flags the format clearly so the next cross-language port doesn't rediscover this trap.