Skip to content

fix(flipcash): treat an unset bill_exchange_data_timeout as absent - #1398

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/user-flags-bill-exchange-timeout-presence
Sep 3, 2026
Merged

fix(flipcash): treat an unset bill_exchange_data_timeout as absent#1398
bmc08gt merged 1 commit into
code/cashfrom
fix/user-flags-bill-exchange-timeout-presence

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

bill_exchange_data_timeout is a google.protobuf.Duration, so an unset field still deserializes to the default instance. UserFlagsMapper read it unconditionally and turned that into 0.seconds, even though the domain field is Duration? and UserFlags.Default uses null. A consumer could not tell "no timeout configured" from "timeout of zero".

This guards the mapping on hasBillExchangeDataTimeout(), which is how every other optional message-typed field in this package is read — UserProfileMapper's profilePicture and joinTs, ChatMetadataMapper's lastMessage.

Why the distinction matters

VerifiedState.exchangeDataFor treats the two values as opposites:

val timeout = billExchangeDataTimeout ?: DefaultBillExchangeDataTimeout // 15.minutes
if (timeout <= Duration.ZERO) return null

So for any user whose flags omitted the field, GiveBillTransactor.start discarded the VerifiedState it already held — however fresh the rate was — and fell into its "rate expired" branch, refetching a verified state on every bill presentation. When that refetch failed (offline, or no rate for the currency/mint pair) the give flow returned ExchangeRateExpiredException while holding a usable rate. With null the held state gets the intended 15 minute window.

A server that deliberately sends 0s still disables verified exchange data, unchanged.

Consumers

No changes needed. Field.BillExchangeDataTimeout already encodes null as -1L and formats it as "None", CachedFlags round-trips it as a nullable Long, and TransactionController's gift-card path never passed the flag — it relies on the same null default.

The one visible difference is the debug flags screen, which now reads "None" rather than "0s" when the server omits the field.

bill_exchange_data_timeout is a google.protobuf.Duration, so an unset field
still yields the default instance and the mapper turned that into 0.seconds
rather than null. Guard on hasBillExchangeDataTimeout(), which is how every
other optional message-typed field here is read — see UserProfileMapper's
profilePicture and joinTs, and ChatMetadataMapper's lastMessage.

The two values are not interchangeable downstream. VerifiedState.exchangeDataFor
substitutes a 15 minute default for null but returns null outright for a timeout
of zero, so every user whose flags omitted the field was treated as having
verified exchange data permanently expired: GiveBillTransactor.start refetched
the rate on every bill instead of using the state it already held.

No consumer needed adjusting. Field.BillExchangeDataTimeout already encodes
null as -1 and formats it as "None", and CachedFlags round-trips it as a
nullable Long.
@bmc08gt bmc08gt self-assigned this Sep 3, 2026
@github-actions github-actions Bot added area: network gRPC, connectivity, API, exchange rates type: fix Bug fix labels Sep 3, 2026
@bmc08gt
bmc08gt merged commit 7a2a839 into code/cash Sep 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: network gRPC, connectivity, API, exchange rates type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant