Skip to content

fix(wallet-toolbox): EntityOutput.mergeNew silently writes basketId undefined on a syncMap idMap miss #543

Description

@E-Jacko

Area

Wallet

Affected package or service and version

@bsv/wallet-toolboxsrc/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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions