taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit d2010a8fe25921c6e467881ac3e3ca3683b141fa
parent 31771451cedb9784cce621d0af5028897546bebf
Author: Florian Dold <dold@taler.net>
Date:   Sat,  5 Sep 2026 13:42:56 +0200

DD71: specify refresh scheduling and exchange key guidance

Define adaptive emergency thresholds, value-first output selection, and
persistent randomized refresh scheduling. Count all value loss when
checking for free refreshes and report expiration risk independently
of the selected rule.

Explain the 720-day spending and 30-day withdrawal guidance and derive
a conditional annual refresh-cost bound with worked acceptance cases.

Issue: https://bugs.taler.net/n/6563
Issue: https://bugs.taler.net/n/7847

Diffstat:
Mdesign-documents/071-auto-refresh.rst | 388+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 282 insertions(+), 106 deletions(-)

diff --git a/design-documents/071-auto-refresh.rst b/design-documents/071-auto-refresh.rst @@ -1,148 +1,324 @@ DD 71: Auto-refresh ################### -:Design status: Accepted +:Design status: Proposed :Implementation status: Prototype :DD shepherd: TBD :Historical contributors: Christian Grothoff :First published: 2025-10-23 -:Last substantive change: 2025-10-23 -:Implementation evidence: ``taler-typescript-core`` (2026-08-19, not merged into the reviewed HEAD) +:Last substantive change: 2026-09-05 +:Implementation evidence: ``taler-typescript-core`` commit ``4889bee049023c59a125973532b847ec60ab6f1b`` (2026-08-19, feature-branch prototype of the earlier policy) Summary ======= This document describes when the wallet should automatically refresh -non-dirty coins. +non-dirty coins, how exchanges should configure denomination keys for this +policy, and how the wallet can bound the cost of keeping a balance. + +It addresses `issue 6563 <https://bugs.gnunet.org/view.php?id=6563>`_, +including predictable refresh costs, and its duplicate +`issue 7847 <https://bugs.gnunet.org/view.php?id=7847>`_, about synchronized +refresh traffic. + +.. note:: + + The prototype implements the earlier policy, not the revisions proposed here. Motivation ========== -The wallet must refresh non-dirty coins before they expire, least the -user looses the money. However, this should not be done too early to -avoid refresh fees and/or excessive load on the exchange. On the other -hand, we need to be careful to not hold off for too long and risk -the wallet not going online before the expiration time. - -We note that there obviously is no perfect solution, as at least in -principle the user could always not restart the wallet until the -expiration time. +The wallet must refresh non-dirty coins before they expire, lest the user +lose money. Refreshing too early causes avoidable fees and exchange load; +waiting too long risks the wallet not going online before expiration. +No policy can protect a wallet that remains offline beyond its coins' +validity periods. Requirements ============ -* Do not loose funds under most conditions -* Do not cause clearly avoidable refresh operations -* Try to educate the user if they are about to get into trouble -* Independently of any specific approach, the wallet - MUST spend those coins first that are earliest to - their expiration time within their equivalence class - as that is always the best way to avoid expiration. -* While lifetimes of denominations are often identical, - that may not always be the case. Theoretically, an - exchange could significantly increase or decrease the - deposit period at any time. The solution should - take this into consideration. +* Preserve funds under ordinary operating conditions, without clearly + avoidable refresh operations. +* Spread automatic refresh traffic across wallets holding the same + denomination, including when an exchange publishes replacement keys. +* Explain expiration risk and the conditions under which refresh-cost + estimates apply. +* Within an equivalence class of otherwise interchangeable coins, the wallet + MUST spend those with the earliest deposit expiration first. +* Account for different denomination lifetimes and changes in the exchange's + offerings. Operator recommendations are not assumptions that the wallet may + make about an arbitrary exchange. Proposed Solution ================= -1. For each denomination, consider if a refresh would - lengthen the expiration date by more than a factor - of four (4x), that is if the deposit expiration time - of the denomination(s) we could currently withdraw - is more than 4x as long as what remains for the - denomination. If so, refresh - all coins of that denomination. - - - .. note:: - - This basically suggests that a refresh - would have a significant positive **impact**. - -2. For each denomination, consider if the remaining - deposit period is less than **6** months, if the - refresh fee would be zero, and if after refreshing - the deposit expiration time would exceed **12** months, - and if we are not on battery power. If so, refresh - all coins of that denomination. - - .. note:: - - This is again a significant impact, and it is basically - gratis for the user. - -3. For each denomination, consider if the remaining - deposit period is less than **3** months, and if - after refreshing the deposit expiration time would - be larger. If so, refresh all coins of that denomination. - If afterwards the expiration time exceeds **12** months, - show the user a warning: - - "This wallet was offline for too long. Make sure to - start it at least every **3 months** to avoid the - risk of loosing funds to expiration." - - .. note:: - - This is basically a last-minute effort (unless we have - an exchange with extremely short expiration periods). - We do not like getting into this situation, so it is - time to educate the user. - -4. Explicitly show a warning in the balances list of - the respective currency if the remaining deposit - period for any coin drops below 90 days. - Distinguish in the warning key causes: - - 1. "We are offline and cannot expand the validity period." - 2. "The payment service provider does not offer longer - validity periods." - - .. note:: - - This should prevent us from getting into trouble if - e-cash is lost anyway. +Lifetimes and output selection +------------------------------ + +A day means 24 hours. The thresholds below use fixed durations of 90, 180, +and 360 days, not calendar months. For an input denomination define:: + + D = stamp_expire_deposit + W = stamp_expire_withdraw + G = D - W + R = D - now + E = min(90 days, G / 4) + +``G`` is the deposit period after withdrawals close; ``E`` is the adaptive +emergency threshold. This policy requires finite timestamps with ``G > 0``. +An invalid lifetime must be reported rather than used to schedule refreshes. +Already-expired coins follow existing expiry/recovery handling; they must not +be treated as ordinary auto-refresh candidates. + +For each eligible input coin, consider feasible, nonempty output selections +from currently withdrawable, verified, non-revoked, non-lost denomination +keys at its exchange. Respect currency, cipher, age restrictions, and refresh +protocol limits. The input must be fresh and available, rather than reserved +for a payment or already participating in a refresh. + +Selections must fit the input value after melting and output withdrawal fees, +as specified by ``/melt`` in the :doc:`exchange API <../core/api-exchange>`:: + + recovered_value = sum of output coin face values + total_cost = input_value - recovered_value + output_expiration = earliest deposit expiration among selected outputs + output_remaining = output_expiration - now + +``total_cost`` includes melting fees, output withdrawal fees, and value that +cannot be represented by the selected denominations. A refresh is free only +if ``total_cost`` is zero, not merely when ``fee_refresh`` is zero. + +Select outputs in this order, before evaluating the refresh rules: + +1. Maximize recovered value. +2. Among equally valuable selections, maximize ``output_expiration``. +3. Among remaining ties, minimize the number of output coins. +4. Break remaining ties by the lexicographic order of sorted denomination + hash/count pairs. + +If the chosen selection cannot extend validity or recover positive value, +do not melt; wait and report expiration risk when applicable. + +The decision and execution MUST use the same selected outputs. Revalidate +input availability, keys, and rule eligibility before melting. If the plan +has become invalid, recompute it rather than silently executing a different +selection. Once a melt has succeeded, finish that refresh using its committed +outputs through the existing refresh lifecycle. + +Refresh rules +------------- + +Evaluate the following rules in priority order using the chosen output +selection. Any matching rule makes the input eligible for scheduled refresh: + +1. ``output_remaining > 4 * R``: the refresh provides a substantial extension. +2. ``R < 180 days``, ``output_remaining > 360 days``, ``total_cost == 0``, and + confirmed external power: opportunistically refresh without losing value. + Unknown power state does not qualify for this rule. +3. ``R < E`` and ``output_expiration > D``: attempt an emergency extension. + +All inequalities are strict. Equality alone does not satisfy a rule; schedule +its next evaluation for the first representable time after the boundary. +Battery state gates only rule 2, not necessary refreshes under rules 1 or 3. +Apply the decision to all available coins of the input denomination with the +same value and compatibility constraints. + +A currently withdrawable output has at least its own ``G`` remaining, well +above its emergency threshold of at most ``G / 4``. This prevents repeated +emergency refreshes merely because slightly newer short-lived keys appear. + +Scheduling and retries +---------------------- + +On first observing eligibility, choose an independent uniform random delay +per wallet and input denomination, using private wallet randomness:: + + urgent_boundary = D - E / 10 + delay_cap = min(1 day, E / 10, (urgent_boundary - now) / 2) + +Before the urgent boundary, draw a delay from zero to ``delay_cap`` and +persist the execution deadline. At or beyond that boundary, attempt refresh +immediately. This spreads traffic across wallets, including when new keys +are published, while retaining time for retries. + +Restarts, checks, and key updates MUST NOT redraw or postpone the deadline. +A missed deadline is due immediately if still eligible. Otherwise retain it +until a valid plan is available, without repeatedly executing an ineligible +task. Move it earlier if needed for urgency; clear it when no applicable fresh +coins remain. Coins of the same denomination may share a wallet's deadline. + +Reevaluate on startup, relevant key or coin changes, external-power changes, +and the next known rule boundary. Revalidate before execution and persist +in-progress state to prevent duplicate refresh groups. Use randomized retry +backoff capped by the time to the urgent boundary; once urgent, cap waits by +remaining deposit lifetime without an additional DD71 delay. Avoid busy loops. +Outages can still prevent successful refresh. + +Warnings +-------- + +Evaluate warnings independently of the selected rule. For ``0 < R < E``, show +expiration risk in the currency balance with the exchange, affected amount, +and earliest expiration. Pending or failing refreshes retain that indication +until successful completion. Distinguish connectivity failures, exchange +errors, unavailable suitable replacements according to fresh key information, +and a pending refresh; do not infer device-wide offline status. + +After successfully refreshing coins whose pre-melt ``R`` was below ``E``, +provide one recovery notice per refresh group, even if rule 1 matched. State +that coins close to expiration were renewed; do not assert that the wallet +was offline. Show the next relevant date, since safe reopening intervals +depend on denomination lifetimes. Already-expired funds use the existing +expiry indication. + +Exchange Configuration Recommendations +====================================== + +For a general-purpose exchange, start with this guidance for every relevant +denomination family:: + + DURATION_SPEND = 720 days + DURATION_WITHDRAW = 30 days + +These are recommendations, not protocol requirements or universal optima. +In the exchange implementation, ``DURATION_SPEND`` runs from withdrawal +expiration to deposit expiration: it is ``G``, not the interval from key +validity start to deposit expiration. With this configuration, a coin acquired +during its withdrawal window has between 720 and 750 days left to deposit. + +Rationale +--------- + +* Replacement validity of 720--750 days makes rule 1 eligible with roughly + 180--187.5 days left, leaving substantial margin before the 90-day emergency + threshold. Under stable offerings, paid refreshes are separated by at least + ``720 - 750 / 4 = 532.5 days`` along each sequence of replacement coins. +* The 30-day issuance window limits replacement-lifetime variation to about + four percent and limits how long a key is used for issuing coins. It does + not eliminate compromise risk during the longer spending period. +* Longer spending periods require more coin-state storage; shorter issuance + windows require more keys and key-management work. These values balance + those costs with occasionally opened wallets, rather than representing a + measured optimum. The 720 days are two fixed 360-day periods, not calendar + years. + +Keep spending lifetimes comparable across denominations, including affordable +change, and fees low enough to permit positive refresh outputs. Maintain +continuous replacement-key coverage for all required values, ciphers, and age +restrictions. Shorter spending periods require more frequent refreshes and +shorter offline intervals; the adaptive rule remains a fallback. + +Follow the :ref:`exchange's denomination-key guidance <Coins-denomination-keys>`: +withdrawal overlap must be below ``DURATION_WITHDRAW``; ``LOOKAHEAD_SIGN`` should +be at least one year, with offline signing at least every six months. Keys +must be signed and published, and are usable only once their validity starts. +For lifetime or fee changes, introduce new configuration sections as the +manual requires, preserving suitable replacements for existing coins. + +Conditional Annual Refresh-Cost Bound +===================================== + +The fee summary should distinguish a conservative upper bound from an expected +charge. The following bound covers keeping the current balance for 365 days +without payments or additional withdrawals. It excludes initial withdrawal +fees, payment-related refreshes, and losses caused by expiration or an outage. +Those risks must not be presented as covered by the estimate. + +Assumptions +----------- + +For this metric, a reasonable denomination/fee structure satisfies: + +* Continuous compatible offerings with stable values, fees, and lifetime + bounds. Unpublished future offerings are projection assumptions, not signed + commitments. +* Every reachable denomination, including small change, admits positive + value-first outputs. Refresh completes while output keys remain withdrawable; + a delayed reveal outside that window invalidates the estimate. +* ``Gmin > 0`` is a lower bound on remaining lifetime at output acquisition; + ``Hmax`` bounds all obtainable output lifetimes from above. Full key lifetime + from validity start to deposit expiration suffices for ``Hmax``. +* ``Delta = Gmin - Hmax / 4 > 0``. +* ``0 <= p < 1`` bounds total loss divided by input value for every paid refresh + of every reachable denomination and projected selection, including output + fees and rounding. + +If these assumptions cannot be established, report the bound as unavailable +and explain why. Recompute or withdraw it when assumptions fail; do not ignore +unrefreshable dust or assume unknown future fees are zero. + +Calculation and justification +----------------------------- + +For current balance ``B`` and horizon ``T = 365 days``, compute:: + + Delta = Gmin - Hmax / 4 + N = 1 + floor(T / Delta) + annual_loss_bound = B * (1 - (1 - p) ** N) + +Round the monetary bound upward to the currency's supported amount precision. +If computing separate bounds per exchange or compatibility group, sum their +rounded bounds. The fee summary must show the horizon and assumptions, and +label the result a conditional upper bound, not a predicted debit or a +promise about future exchange offerings. + +A new coin starts with at least ``Gmin`` remaining. Neither paid rule can +match above ``Hmax / 4``: rule 1 requires a fourfold extension, and rule 3 has +``E <= G / 4 <= Hmax / 4``. Paid refreshes are therefore separated by at least +``Delta`` along each sequence of replacement coins. Free refreshes cannot +shorten that interval; scheduling delays only lengthen it. The initial ``1`` +allows an immediately due refresh of existing holdings. + +Each paid refresh preserves at least ``1 - p`` of its input, including when +it splits into multiple outputs. Since every resulting sequence has at most +``N`` paid refreshes, at least ``B * (1 - p) ** N`` remains. If ``p == 0``, the +bound is zero. + +With the recommended configuration, ``Gmin = 720 days``, ``Hmax = 750 days``, +and ``Delta = 532.5 days``, giving ``N = 1``. If the total loss per paid refresh +is bounded by one percent across all reachable denominations, a balance of +100 currency units therefore has a conditional annual bound of 1 unit. The +one-percent premise must be checked; the key durations alone do not imply it. Definition of Done ================== -* [x] Prototype implemented in a wallet-core feature branch -* [ ] Prototype merged into the main branch -* [ ] Changes to interactions for signalling warnings to GUIs -* [ ] Dev experiments exist to trigger special alerts to users -* [ ] GUIs have been designed and tested - +* [x] Earlier policy prototyped in a wallet-core feature branch +* [ ] Revised policy reviewed, implemented, and merged +* [ ] Tests cover adaptive and strict thresholds, mixed/empty outputs, fees and + rounding, power state, persistent scheduling, retries, and warning overlap +* [ ] Fee-bound tests cover zero cost, split outputs, initially due refreshes, + and unavailable estimates +* [ ] Exchange guidance checked against supported key rotation +* [ ] GUI warnings and fee summaries designed and tested with dev experiments +* [ ] Implemented behavior documented in the wallet reference Alternatives ============ -The wallet currently implements simple rules for auto-refresh: +The older wallet policy checked an exchange after 75% of the period from +withdrawal expiration to deposit expiration had passed, refreshing coins more +than 50% into that period. Grouping these operations reduced separate checks, +but did not account for replacement lifetimes or absolute offline margins. -1. After 75% of a denomination's "deposit lifespan" has passed, - we do "auto-refresh check" for all coins of the exchange - -2. During this auto-refresh check, all coins that are >50% into - their deposit lifespan are auto-refreshed. - -This is risky as it does not consider absolute lifespans or user -behavior. +The earlier DD71 policy accepted any extension below a fixed 90-day threshold. +That could repeatedly refresh short-lived coins whenever slightly newer keys +appeared. The adaptive threshold retains an emergency path without treating +all short-lived coins as perpetual emergencies. Drawbacks ========= -* This approach does not (yet) consider user behavior. We could - theoretically learn from that. - - - -Discussion / Q&A -================ - -(This should be filled in with results from discussions on mailing lists / personal communication.) +* This policy does not learn individual wallet usage patterns. +* Value-first selection can defer a lifetime extension that would cost more. +* The annual bound can be conservative or unavailable, especially with changing + offerings or unrefreshable small denominations. +* Independent scheduling reduces synchronized traffic; overdue startup checks, + widespread outages, and urgent retries can still create bursts. +* Longer spending lifetimes increase exchange storage obligations. Operator + recommendations must balance those costs against wallet availability.