Skip to content

fix: Confirmed internalizeAction with StorageIdb fails after asynchronous header lookup #917

Closed

Description

@jdr377

Area

Wallet

Affected package or service and version

Official @bsv/wallet-toolbox-client@2.14.6 with official @bsv/sdk@2.8.11, local StorageIdb, and stock ChaintracksServiceClient over HTTP. Also reproduced in official client 2.13.2; the same tested composition works in 2.13.1.

Defect

The first internalizeAction for a valid confirmed transaction can fail in IndexedDB after an asynchronous header lookup:

InvalidStateError: Failed to execute 'objectStore' on 'IDBTransaction': The transaction has finished.

We reproduced this with both a deterministic historical fixture and a real 500-satoshi mainnet deposit, confirmed while the receiving browser wallet was closed. In the live test, proof verification succeeded, but the first wallet payment import failed and no subject transaction/proof/request/output records were created.

Minimal reproduction

Runnable reproduction, complete evidence bundles and reports.

Clone or download that gist, then run with Node 24.11 or newer:

npm ci --ignore-scripts --strict-peer-deps --no-audit --no-fund
npx --no-install playwright install chromium
npm test

No ts-stack build, funded wallet or account is required. The sample uses official packages, public APIs, actual Chromium IndexedDB and authentic historical transaction/header bytes. It creates six isolated browser contexts/origins. Three confirmed HTTP cases fail with the exact native exception; a validated-header-cache control accepts identical transaction evidence and stores its records. Invalid-header and invalid-proof-of-work controls reject before internalization.

npm test passes when it reproduces the pinned release's defect and all controls behave as expected; it does not assert repaired behavior. The public call is:

await wallet.internalizeAction({
  tx: atomicBeef,
  description: 'Historical proof storage reproduction',
  outputs: [
    {
      outputIndex: 0,
      protocol: 'basket insertion',
      insertionRemittance: { basket: 'historical-proof-fixture' }
    }
  ]
})

The SDK verifies the subject BUMP before the failing call. The historical genesis output is basket data; ownership or spendability is not claimed. The gist includes exact locks, full stacks, provider observations, request traces, screenshots and registry/provenance receipts. node restore-bundles.mjs restores the three ZIP bundles with SHA-256 verification; the runnable sample is also provided directly as text files.

Expected behavior

The browser package documents local IndexedDB support. Valid confirmed evidence should be internalized and its proof, transaction and requested output durably stored. The identical-input cache control succeeds, and the adjacent previous release accepts the HTTP cases.

Actual behavior and evidence

Mainnet test

Transaction fd878f9435b2203a09e85155ecd3db1d0bf4cf529182efccaf4baaffa735ba17, output 0, paid 500 satoshis to the disposable test address. It confirmed in block 00000000000000000000491df49098a82b07bd07f3088c4133f0ab52e07a3016 at height 970342 while the receiving wallet was closed. The public confirmation was checked before reopening it.

The first import, recorded at 2026-10-09T17:20:40.097Z (October 10 in Australia/Sydney), used stock live Services: WhatsOnChain supplied the raw transaction and Merkle proof, and the public mainnet ChainTracks service supplied the headers. Browser HTTP cache was cleared/disabled and service workers blocked. No wallet monitor, address scanner, remote storage, message box, header fixture, library patch, artificial delay or database keepalive was used.

The TXID, receive script, exact 500-satoshi output and proof were checked; BEEF verification returned true. wallet.internalizeAction with protocol wallet payment and retained derivation remittance then failed with the same native exception and stack:

StorageIdb.filterProvenTxs
StorageIdb.findProvenTxs
StorageIdb.findOrInsertProvenTx
InternalizeActionContext.findOrInsertProvenTxFromBump
StorageIdb.transaction
InternalizeActionContext.newInternalize

Subject transactions, proven transactions, proof requests and outputs were empty before and after. All observed HTTP requests were GETs; there were no blocked requests or uncaught page errors. No retry or live cache-control import was performed. The wallet closed afterward, and a subsequent public UTXO read still showed the output unspent. This establishes failed wallet recording, not loss of the on-chain output.

LIVE_MAINNET_REPORT.md and mainnet-first-import.json in the gist contain the assessment and full first-attempt receipt. The live ZIP also includes independent confirmation/header/UTXO reads and an offline validator. It excludes the test key and browser profile. This was a plain address deposit with BRC-29-compatible remittance; authenticated BRC-29 sender/message-box delivery remains untested.

Cause and introduction

The installed client opens a writer transaction and passes its token to findOrInsertProvenTxFromBump, which awaits getHeaderForHeight before accessing the proof store through that token. The exception, ordering and successful cache control support an IndexedDB transaction-lifetime cause. Retrieving/validating network evidence before the writer scope may address it while preserving atomic database operations and race checks; no fix is included.

Commit b3155fa2e86c1de9606a3da8b4f36c984aa46fb7, September 22, 2026, introduces that enclosing transaction and advances the client manifest from 2.13.1 to 2.13.2. In adjacent official-release browser campaigns, all three valid HTTP cases succeed in 2.13.1 and fail in 2.13.2; cache controls succeed in both. Both use compatible SDK 2.8.11.

The 2.13.2 comparison supplies its public fetch option as (input, init) => window.fetch(input, init) to avoid a separate default-transport Illegal invocation setup failure. It retains real HTTP and patches no dependency. HISTORY_REPORT.md documents this prerequisite, artifact/source attribution and limitations. The source commits were not separately built and runtime-bisected.

Environment

Windows 10.0.19045 x64; Node 24.19.0; npm 10.9.4; Chromium 151.0.7922.34; Playwright 1.62.1; esbuild 0.28.2; idb 8.0.4. Strict peer installation succeeded. No IndexedDB emulator or patched wallet dependency was used. The deterministic campaign uses isolated fresh contexts; the live test uses a separate persistent profile, initially empty stores and cleared/disabled HTTP cache.

Safety checks

  • I searched for an existing issue or pull request describing this defect.
  • This report contains no secret, private key, token, production data, or uncoordinated vulnerability detail.

Duplicate searches were refreshed October 10, 2026, including the exact finished-transaction error (0 results). Related reports/PRs concern array filters (#791/#792), custom-instruction encryption (#785/#818), SQLite settings-transaction propagation (#444), unproven broadcasting (#856) or broader sync/reliability work (#569). No matching fix was identified.

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