taler-docs

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

commit e6ebf74276da56eed9993f9e62202c9a4dfb57dd
parent b2d59fb05eaf44f1a3672f0a830a370fd492411d
Author: Iván Ávalos <avalos@disroot.org>
Date:   Sat,  8 Aug 2026 17:18:14 +0200

dd92: implement the transaction-family increments and document the reserves proposal

Diffstat:
Mdesign-documents/092-incremental-backup-sync.rst | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 115 insertions(+), 27 deletions(-)

diff --git a/design-documents/092-incremental-backup-sync.rst b/design-documents/092-incremental-backup-sync.rst @@ -1047,8 +1047,8 @@ User initiates a withdrawal. instructedAmount: AmountString; } -.. TODO: store reserves in backup? - (in wallet-core DB, exchangeBaseUrl is contained there) +.. TODO: store reserves in backup? (see the proposed "add-reserve" + increment type below; withdrawal-start is blocked on it) * **Primary key:** ``[withdrawalGroupId]`` * **Deletion groups:** ``[withdrawals]`` @@ -1126,6 +1126,76 @@ Store all ``failReason`` in the database. .. TODO: withdrawal (soft) deletion as increment? (can't be easily deleted because of coin references) +Add a reserve +~~~~~~~~~~~~~ + +*Proposed* -- a reserve is created by the wallet for every withdrawal and +for the merge capability of P2P payments, and its key pair lives in the +wallet's ``reserves`` object store (see the ``WalletReserve`` record in +``db.ts``). The increment type below is what that store should map to; it +is **not implemented yet** and its schema is a proposal. + +.. ts:def:: AddReserveInc + + interface AddReserveInc { + type: "add-reserve"; + exchangeBaseUrl: string; + reservePub: EddsaPublicKey; + reservePriv: EddsaPrivateKey; + } + +* **Primary key:** ``[exchangeBaseUrl, reservePub]`` +* **Deletion groups:** ``[reserves, exchanges]`` + +Merge strategy +++++++++++++++ + +No merge is required: the key pair of a reserve is generated once and never +changes. + +The increment carries only the key material and the exchange it belongs to. +The remaining fields of ``WalletReserve`` (``status``, the KYC thresholds, +``kycAccessToken``, ``amlReview``) are all derivable by querying the +exchange and are deliberately not backed up, so that a restored wallet +re-derives them instead of trusting stale state. Note that the current +``WalletReserve`` record does not store the exchange base URL; the +``FIXME: Should reference exchange.`` comment in ``db.ts`` and the +redundant ``exchangeBaseUrl`` of ``WithdrawalGroupRecord`` both point at +this gap. The increment requires the field, so adding the increment type +is paired with fixing the record. + +Why the schema matters to the other increment types +++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The ``reserves`` store is referenced, directly or through its row id, by +the withdrawal groups (``reservePub``/``reservePriv``), the coin sources +(``WithdrawCoinSource.reservePub``, used for recouping), the exchange +entries (``currentMergeReserveRowId``) and the peer-pull-credit records +(``mergeReserveRowId``). The absence of a reserve increment type blocks +the increment types that touch those records: + +* ``withdrawal-start`` is the most obvious case: the wallet's + ``WithdrawalGroupRecord`` embeds the reserve key pair and the exchange + base URL, and the design above had an open TODO asking whether the + reserve goes into the backup at all. With ``add-reserve``, a + ``withdrawal-start`` increment can reference the reserve by + ``[exchangeBaseUrl, reservePub]`` instead of carrying the key pair, + avoiding duplication -- though carrying it inline would also work, since + the withdrawal record already stores it redundantly. +* ``add-coin`` / ``sign-coin`` / ``spend-coin`` reference the reserve + through the withdrawal coin source's ``reservePub``, without which a + restored coin cannot be recouped; and their deletion group includes + ``withdrawals``, so the cascade only makes sense once withdrawals are + backed up. +* ``peer-pull-credit`` stores ``mergeReserveRowId``, a row id into the + ``reserves`` store; without the reserve records the id is dangling on + restore. + +The increment types that do **not** depend on the reserves store -- the +deposit, merchant-payment, peer-push-credit, peer-push-debit and +peer-pull-debit families -- are implemented; see the "Definition of done" +section. + Start a deposit ~~~~~~~~~~~~~~~ @@ -1224,30 +1294,36 @@ Start a merchant payment User initiates a payment to a merchant. -.. ts:def:: PaymentStartInc - - interface PaymentStartInc { - type: "payment-start"; - proposalId: string; - claimToken?: string; - downloadSessionId?: string; - repurchaseProposalId?: string; - noncePub: EddsaPublicKey; - noncePriv: EddsaPrivateKey; - secretSeed: string; - exchanges?: string[]; - contractTermsHash: string; // blob - timestamp: TalerPreciseTimestamp; - - // Donau - donauOutputIndex?: number; - donauBaseUrl?: string; - donauAmount?: AmountString; - donauTaxIdHash?: string; - donauTaxIdSalt?: string; - donauTaxId?: string; - donauYear?: string; - } + .. ts:def:: PaymentStartInc + + interface PaymentStartInc { + type: "payment-start"; + proposalId: string; + // Not in the original design, but needed to reconstruct the + // `taler://pay/...' URI and re-download the proposal on restore: + merchantBaseUrl: string; + orderId: string; + claimToken?: string; + downloadSessionId?: string; + repurchaseProposalId?: string; + noncePub: EddsaPublicKey; + noncePriv: EddsaPrivateKey; + secretSeed: string; + exchanges?: string[]; + // Hash of the contract terms (a blob). Unknown until the + // proposal has been downloaded. + contractTermsHash?: string; + timestamp: TalerPreciseTimestamp; + + // Donau + donauOutputIndex?: number; + donauBaseUrl?: string; + donauAmount?: AmountString; + donauTaxIdHash?: string; + donauTaxIdSalt?: string; + donauTaxId?: string; + donauYear?: number; + } * **Primary key:** ``[proposalId]`` * **Deletion groups:** ``[payments]`` @@ -2013,7 +2089,19 @@ Definition of done cycle with its pull/merge/apply half, the API request handlers, the account payment flow and item deletion (retro-redaction of the ``originBlocks`` plus the pull-side "deleted iff absent from all origin blocks" sweep) are done; - the remaining increment types still missing). + of the increment types, the exchange, global-trust, bank-account, donau and + denomination families as well as the deposit, merchant-payment, + peer-push-credit, peer-push-debit and peer-pull-debit families are + implemented. The contract terms of those transactions are backed up as + blobs (uploaded ahead of the referencing blocks with their reference-count + adjustments, and fetched and stored back into the contract-terms store on + the pull side); a transaction whose terms are not available is shown in a + reduced form instead of failing the transaction listing. The withdrawal + and coin families are blocked on the still undefined "reserves" increment + type (proposed above), and the token family on a schema redesign: the + increments in this document model seed-derived tokens, while the wallet + database now stores per-token key material, so restoring from the + documented increments could not produce spendable tokens). * [x] Design sync API (+ auth). * [ ] Server-side implementation (partial: block GET/POST/PUT/DELETE, object store GET/POST with reference counting, /config and payments done;