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:
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;