Area
Wallet
Affected package or service and version
@bsv/wallet-toolbox — src/storage/schema/entities/EntityOutput.ts, mergeNew / mergeExisting. Current on main at 82ab4d3c.
Defect
mergeNew maps the incoming output's basketId through the sync map:
this.basketId = this.basketId ? syncMap.outputBasket.idMap[this.basketId] : undefined
When the idMap has no entry for a non-null source basketId, the lookup yields undefined and the output is inserted belonging to no basket, with no error raised.
A later full re-sync cannot repair it: mergeExisting never assigns basketId at all — its update block sets spentBy, spendable, change, type, providedBy, purpose, outputDescription, spendingDescription, senderIdentityKey, customInstructions, scriptLength, scriptOffset and lockingScript, and nothing else.
Minimal reproduction
Call mergeNew with a sync map whose outputBasket.idMap lacks the source basket id, and capture what reaches storage.insertOutput:
idMap HIT : inserted basketId = 77
idMap MISS: inserted basketId = undefined (no throw)
mergeExisting assigns basketId: false
Both runs are identical apart from the presence of syncMap.outputBasket.idMap[7]; the transaction id maps in both, so the miss is isolated to the basket.
Expected behavior
An idMap miss for a non-null source basketId means the sync chunk is incoherent. It should be an error, or at minimum surfaced in the merge result — not silently coerced to "no basket".
Actual behavior and evidence
The output is inserted with basketId: undefined and nothing reports it. Downstream, it silently disappears from basket-scoped queries (listOutputs by basket), and any application semantics attached to baskets degrade with no signal. A reseed does not heal it, per mergeExisting above.
On reachability — stated as a hypothesis, not a measurement. The above demonstrates the behaviour on a miss; I have not produced a live sync that causes the miss. The candidate is offset-based paging: entities page per type with a fixed since, so a basket created at the source after the basket pass has advanced could have its outputs arrive while its own row does not. I have not proven that sequence, and it should not be taken as proven.
For cross-implementation context, bsv-blockchain/go-wallet-toolbox#852 is the same class on the Go side — provenTx/provenTxReq idMaps never populated, the TS client then writing undefined into transaction.provenTxId — filed there as "High — silent data corruption". That is an argument that unpopulated-idMap writes are worth failing loudly on, in both implementations.
Environment
Reproduced against a built 2.10.2 artifact under Node 22; the cited code is byte-identical on main at 82ab4d3c.
Area
Wallet
Affected package or service and version
@bsv/wallet-toolbox—src/storage/schema/entities/EntityOutput.ts,mergeNew/mergeExisting. Current onmainat82ab4d3c.Defect
mergeNewmaps the incoming output'sbasketIdthrough the sync map:When the idMap has no entry for a non-null source
basketId, the lookup yieldsundefinedand the output is inserted belonging to no basket, with no error raised.A later full re-sync cannot repair it:
mergeExistingnever assignsbasketIdat all — its update block setsspentBy,spendable,change,type,providedBy,purpose,outputDescription,spendingDescription,senderIdentityKey,customInstructions,scriptLength,scriptOffsetandlockingScript, and nothing else.Minimal reproduction
Call
mergeNewwith a sync map whoseoutputBasket.idMaplacks the source basket id, and capture what reachesstorage.insertOutput:Both runs are identical apart from the presence of
syncMap.outputBasket.idMap[7]; the transaction id maps in both, so the miss is isolated to the basket.Expected behavior
An idMap miss for a non-null source
basketIdmeans the sync chunk is incoherent. It should be an error, or at minimum surfaced in the merge result — not silently coerced to "no basket".Actual behavior and evidence
The output is inserted with
basketId: undefinedand nothing reports it. Downstream, it silently disappears from basket-scoped queries (listOutputsby basket), and any application semantics attached to baskets degrade with no signal. A reseed does not heal it, permergeExistingabove.On reachability — stated as a hypothesis, not a measurement. The above demonstrates the behaviour on a miss; I have not produced a live sync that causes the miss. The candidate is offset-based paging: entities page per type with a fixed
since, so a basket created at the source after the basket pass has advanced could have its outputs arrive while its own row does not. I have not proven that sequence, and it should not be taken as proven.For cross-implementation context,
bsv-blockchain/go-wallet-toolbox#852is the same class on the Go side —provenTx/provenTxReqidMaps never populated, the TS client then writingundefinedintotransaction.provenTxId— filed there as "High — silent data corruption". That is an argument that unpopulated-idMap writes are worth failing loudly on, in both implementations.Environment
Reproduced against a built 2.10.2 artifact under Node 22; the cited code is byte-identical on
mainat82ab4d3c.