Skip to content

fix: key received pre-activity metadata by tx_id, not address #135

Description

@jvsena42

Found while wiring hardware-wallet tag backup on Android (synonymdev/bitkit-android#1163, follow-up to #113). Verified against v0.5.5; the read-side half reproduced on regtest with the bitkit-docker Trezor emulator.

Problem

For received activities, address is treated as the identity of a pre-activity metadata record on both the write and the read side. Because a receive address can legitimately serve many transactions, at most one received record per (wallet_id, address) can survive, and the one that is read back is arbitrary.

The primary key does not say this. It is (wallet_id, payment_id), so callers reasonably expect one record per transaction.

Write side: storing a record deletes its siblings

upsert_pre_activity_metadata deletes by address for every record it writes:

DELETE FROM pre_activity_metadata
 WHERE wallet_id = ?1 AND address = ?2 AND is_receive = 1

So writing N received records that share one address leaves only the last one. Every earlier record is silently discarded even though each has a distinct payment_id. add_pre_activity_metadata does the same thing for a single record.

For Android this means a user who tagged several received transactions on the same address gets one tag set back after a restore, and loses the rest, with nothing reported.

Read side: re-attachment picks an arbitrary sibling

apply_pre_activity_metadata_for_onchain picks the lookup key from the payment direction:

PaymentType::Received => transfer(..., &activity.address, activity_id, true)   // WHERE address = ? AND is_receive = 1
PaymentType::Sent     => transfer(..., &activity.tx_id,   activity_id, false)  // WHERE payment_id = ?

Received activities match on address alone, so the metadata attaches to whichever transaction is found first.

Reproduced on regtest: a hardware wallet address reused across ten received transactions. Metadata stored against transaction 5e23be45… (5009 sats) re-attached to sibling transaction 9af07885… (5000 sats) on the same address. Right wallet, right address, wrong transaction.

Why address-keying is load-bearing today

Consumers cannot work around either half. apply_pre_activity_metadata_for_onchain selects the branch from the activity's tx_type, so a received activity is only ever looked up by address. A consumer that stores a received record without an address to avoid the delete would simply never have it re-attached.

The result is that a received transaction's metadata cannot be addressed individually, in either direction.

Requested change

Use tx_id as the identity when it is present, on both sides:

  • Write: only replace by address when the incoming record has no tx_id. When it does, let (wallet_id, payment_id) be the key it already claims to be, so sibling transactions on the same address coexist.
  • Read: prefer an exact tx_id match, falling back to the address match when the record has no tx_id.

Genuine pre-activity records — staged before a transaction exists — have no tx_id yet, so their current replace-by-address and match-by-address behaviour is preserved. The change only affects records that already name a transaction.

Acceptance criteria

  • Writing several received records that share an address but have distinct tx_ids keeps all of them.
  • Writing a received record with no tx_id still replaces the existing address-keyed record, as today.
  • Metadata carrying a tx_id re-attaches to that exact transaction.
  • Metadata without a tx_id keeps the current address behaviour.
  • A reused receive address with several received transactions attaches each record to its own transaction.
  • Tests cover both keys on both sides, including several tagged receives to one address surviving a write-then-reattach round trip.

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