Found while wiring hardware-wallet tag backup on Android (synonymdev/bitkit-android#1163, follow-up to #113). Verified against v0.5.5; reproduced on regtest with the bitkit-docker Trezor emulator.
Problem
upsert_tags opens a single transaction and returns Err as soon as any one record fails, so one unusable tag discards every other tag in the same call.
That matters because the apps restore activities, tags and closed channels together. On Android a single tag whose parent activity did not exist produced:
Failed to restore activity backup [errorDetails=Failed to insert tag: FOREIGN KEY constraint failed]
The whole category was lost — activities and closed channels included, not just the offending tag. Measured on device, activity_tags went 1 to 0 and closed channels were skipped entirely.
The apps can and should isolate their own calls, and Android is doing that separately. But the batch semantics still let one bad row destroy an arbitrary amount of good data, and every consumer otherwise has to write a per-record loop purely to get resilience.
Requested change
Persist the records that can be written and report the rest, rather than failing the batch — for example returning { inserted, skipped[] }.
Acceptance criteria
- A bulk tag upsert containing one unusable record still persists the remaining records.
- The caller can tell which records were skipped and why.
- Tests cover a mixed batch of valid and unusable records.
Found while wiring hardware-wallet tag backup on Android (synonymdev/bitkit-android#1163, follow-up to #113). Verified against
v0.5.5; reproduced on regtest with thebitkit-dockerTrezor emulator.Problem
upsert_tagsopens a single transaction and returnsErras soon as any one record fails, so one unusable tag discards every other tag in the same call.That matters because the apps restore activities, tags and closed channels together. On Android a single tag whose parent activity did not exist produced:
The whole category was lost — activities and closed channels included, not just the offending tag. Measured on device,
activity_tagswent 1 to 0 and closed channels were skipped entirely.The apps can and should isolate their own calls, and Android is doing that separately. But the batch semantics still let one bad row destroy an arbitrary amount of good data, and every consumer otherwise has to write a per-record loop purely to get resilience.
Requested change
Persist the records that can be written and report the rest, rather than failing the batch — for example returning
{ inserted, skipped[] }.Acceptance criteria