Skip to content

fix(accounting): resolve the return buyCrypto via the chargeback relation#4259

Closed
Danswar wants to merge 1 commit into
developfrom
fix/bank-tx-return-chargeback-relation
Closed

fix(accounting): resolve the return buyCrypto via the chargeback relation#4259
Danswar wants to merge 1 commit into
developfrom
fix/bank-tx-return-chargeback-relation

Conversation

@Danswar

@Danswar Danswar commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Since the cutover every BUY_CRYPTO_RETURN booking fails with "bank_tx X BUY_CRYPTO_RETURN without buyCrypto.amountInChf" — and because the consumer is head-of-line-blocking, the bank_tx watermark is wedged: nothing after bank_tx 206858 gets booked.

The data is intact: the returns' buy_cryptos (128422 / 128912) carry their amountInChf. The bug is the relation — a return bank_tx is linked from buy_crypto.chargebackBankTx, so tx.buyCrypto (the inbound-payment inverse, which buyCryptoOwedChf reads) is null on returns. The entity itself models this (bankTx.buyCryptoChargeback, and its user getter already tries both).

Fix: load buyCryptoChargeback in both consumer queries and prefer it in buyCryptoOwedChf (fallback to buyCrypto unchanged, message unchanged).

The existing return specs set tx.buyCrypto directly and so encoded the wrong relation; the new spec mirrors the prod linkage (buyCrypto null, buyCryptoChargeback set) — verified red on the previous code, green with the fix (71/71).

Expected after deploy: the two prod returns book, the bank_tx watermark advances, and the content-change scan gate-blocked retries on buy_crypto 128422 / buy_fiat 68748 clear.

…tion

A BUY_CRYPTO_RETURN bank_tx is linked from buy_crypto.chargebackBankTx;
tx.buyCrypto is the inbound-payment inverse and is null on returns. The
owed-CHF anchor therefore threw 'without buyCrypto.amountInChf' on every
return since the cutover (prod: bank_tx 206858/206864 → buy_crypto
128422/128912, whose amountInChf is present), wedging the bank_tx
watermark head-of-line. Load buyCryptoChargeback in both consumer queries
and prefer it in buyCryptoOwedChf (fallback to buyCrypto unchanged).

The existing return specs set tx.buyCrypto directly and so encoded the
wrong relation; the new spec mirrors the prod linkage (buyCrypto null,
buyCryptoChargeback set) and fails on the previous code.

@TaprootFreak TaprootFreak left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — verified against the code and against prod (read-only)

Correct, well-targeted fix. The wedge and the fix both check out.

Root cause confirmed. A return bank_tx is linked from buy_crypto.chargebackBankTx, so tx.buyCrypto (the inbound-payment inverse that buyCryptoOwedChf read) is null on returns → the old code threw. Preferring buyCryptoChargeback resolves the right anchor.

Verified against PRD data:

bank_tx type as inbound as chargeback tx.buyCrypto amountInChf
206858 BuyCryptoReturn 0 1 null 9226.11 (bc 128422)
206864 BuyCryptoReturn 0 1 null 9.25 (bc 128912)

Exactly the shape the fix handles. The new spec's 9226.11 mirrors buy_crypto 128422 to the cent; totalFeeAmountChf is NULL on both → ?? 0 → owed = 9226.11.

Adversarial checks, all clean:

  • buyCryptoChargeback relation exists on the entity; the user getter already tried both — consistent.
  • buyCryptoOwedChf has a single caller, buyCryptoReturnLegs (§4.2a, return-only) — the ?? preference can't misroute a non-return.
  • No unguarded third path: both queries that feed booking — the §4.12 content-change scan and the processForward backfill — now load the relation. The head-of-line break in processForward is what made 206858 a hard wedge (nothing after it books); this closes it.
  • All three usages inside buyCryptoOwedChf switched consistently.

Two non-blocking notes:

  1. The throw message still reads without buyCrypto.amountInChf while the check now uses the chargeback relation — fine to keep for log-grep stability, just slightly stale.
  2. The spec sets totalFeeAmountChf: 0; prod has NULL. Functionally identical via ?? 0, but null would mirror prod exactly.

Expected after deploy: the two returns book, the bank_tx watermark advances past 206858, and the gate-blocked content-change retries clear. Same class as the cutover relation bug (#4253).

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Superseded by #4261 (merged to develop at 18:37) — same root cause, resolved more completely, so closing this.

#4261 folds in the two notes from my review here:

  • reads tx.buyCryptoChargeback directly instead of ?? tx.buyCrypto — on a BUY_CRYPTO_RETURN the incoming buyCrypto inverse is always null, so the fallback was dead code
  • corrects the throw message to without buyCryptoChargeback.amountInChf
  • fixes all the pre-existing return specs that encoded the wrong relation, not just adding one new spec

Verified equivalent at the fix site (both queries load the buyCryptoChargeback relation; buyCryptoOwedChf is return-only). No behavioural loss in closing this.

Note for tracking: the wedge is still live on PRD — #4261 is on develop, not main, and the running prod image predates it (returns 206858/206864 still unbooked, ledgerWatermark.bank_tx frozen at 206858). It clears once #4261 reaches prod via the release PR #4256 (developmain).

@TaprootFreak

Copy link
Copy Markdown
Collaborator

For coordination (process note, no blame): this overlapped with independent work on the same bug — #4259 and #4261 fixed the identical wrong-relation read in parallel. Worth a quick heads-up in the channel next time a ledger fix is in flight, so two branches don't chase the same wedge. Nothing to action here; #4261 is the one that shipped.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants