From 4b0680eefa0dcb2e9b00342949393e4b166eecb2 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 12 Sep 2023 13:48:52 +0200 Subject: [PATCH] wallet-core: move contract terms to object store --- packages/taler-wallet-core/src/db.ts | 21 ++-- packages/taler-wallet-core/src/dbless.ts | 24 ++-- .../src/operations/deposits.ts | 35 ++++-- .../src/operations/pay-peer-pull-debit.ts | 117 ++++++++---------- .../src/operations/transactions.ts | 42 +++++-- 5 files changed, 142 insertions(+), 97 deletions(-) diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 04c3ce723..9bf9a29cc 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -28,6 +28,7 @@ import { } from "@gnu-taler/idb-bridge"; import { AgeCommitmentProof, + AmountJson, AmountString, Amounts, AttentionInfo, @@ -1640,6 +1641,15 @@ export interface DepositTrackingInfo { export interface DepositGroupRecord { depositGroupId: string; + currency: string; + + /** + * Instructed amount. + */ + amount: AmountString; + + wireTransferDeadline: TalerProtocolTimestamp; + merchantPub: string; merchantPriv: string; @@ -1655,13 +1665,6 @@ export interface DepositGroupRecord { salt: string; }; - /** - * Verbatim contract terms. - * - * FIXME: Move this to the contract terms object store! - */ - contractTermsRaw: MerchantContractTerms; - contractTermsHash: string; payCoinSelection: PayCoinSelection; @@ -1981,7 +1984,9 @@ export interface PeerPullPaymentIncomingRecord { exchangeBaseUrl: string; - contractTerms: PeerContractTerms; + amount: AmountString; + + contractTermsHash: string; timestampCreated: TalerPreciseTimestamp; diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 11c6c0f74..65c293bdf 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -33,12 +33,13 @@ import { AmountString, codecForAny, codecForBankWithdrawalOperationPostResponse, - codecForDepositSuccess, + codecForBatchDepositSuccess, codecForExchangeMeltResponse, codecForExchangeRevealResponse, codecForWithdrawResponse, DenominationPubKey, encodeCrock, + ExchangeBatchDepositRequest, ExchangeMeltRequest, ExchangeProtocolVersion, ExchangeWithdrawRequest, @@ -256,22 +257,27 @@ export async function depositCoin(args: { refundDeadline: refundDeadline, wireInfoHash: hashWire(depositPayto, wireSalt), }); - const requestBody = { - contribution: Amounts.stringify(dp.contribution), + const requestBody: ExchangeBatchDepositRequest = { + coins: [ + { + contribution: Amounts.stringify(dp.contribution), + coin_pub: dp.coin_pub, + coin_sig: dp.coin_sig, + denom_pub_hash: dp.h_denom, + ub_sig: dp.ub_sig, + }, + ], merchant_payto_uri: depositPayto, wire_salt: wireSalt, h_contract_terms: contractTermsHash, - ub_sig: coin.denomSig, timestamp: depositTimestamp, wire_transfer_deadline: wireTransferDeadline, refund_deadline: refundDeadline, - coin_sig: dp.coin_sig, - denom_pub_hash: dp.h_denom, merchant_pub: merchantPub, }; - const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url); - const httpResp = await http.postJson(url.href, requestBody); - await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); + const url = new URL(`batch-deposit`, dp.exchange_url); + const httpResp = await http.fetch(url.href, { body: requestBody }); + await readSuccessResponseJsonOrThrow(httpResp, codecForBatchDepositSuccess()); } export async function refreshCoin(req: { diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index a3483a332..2de8f30a1 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -884,8 +884,18 @@ async function processDepositGroupPendingDeposit( ): Promise { logger.info("processing deposit group in pending(deposit)"); const depositGroupId = depositGroup.depositGroupId; + const contractTermsRec = await ws.db + .mktx((x) => [x.contractTerms]) + .runReadOnly(async (tx) => { + return tx.contractTerms.get(depositGroup.contractTermsHash); + }); + if (!contractTermsRec) { + throw Error("contract terms for deposit not found in database"); + } + const contractTerms: MerchantContractTerms = + contractTermsRec.contractTermsRaw; const contractData = extractContractData( - depositGroup.contractTermsRaw, + contractTermsRec.contractTermsRaw, depositGroup.contractTermsHash, "", ); @@ -921,12 +931,11 @@ async function processDepositGroupPendingDeposit( coins, h_contract_terms: depositGroup.contractTermsHash, merchant_payto_uri: depositGroup.wire.payto_uri, - merchant_pub: depositGroup.contractTermsRaw.merchant_pub, - timestamp: depositGroup.contractTermsRaw.timestamp, + merchant_pub: contractTerms.merchant_pub, + timestamp: contractTerms.timestamp, wire_salt: depositGroup.wire.salt, - wire_transfer_deadline: - depositGroup.contractTermsRaw.wire_transfer_deadline, - refund_deadline: depositGroup.contractTermsRaw.refund_deadline, + wire_transfer_deadline: contractTerms.wire_transfer_deadline, + refund_deadline: contractTerms.refund_deadline, }; for (let i = 0; i < depositPermissions.length; i++) { @@ -1086,7 +1095,10 @@ async function trackDeposit( coinPub: string, exchangeUrl: string, ): Promise { - const wireHash = depositGroup.contractTermsRaw.h_wire; + const wireHash = hashWire( + depositGroup.wire.payto_uri, + depositGroup.wire.salt, + ); const url = new URL( `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${coinPub}`, @@ -1358,8 +1370,9 @@ export async function createDepositGroup( const depositGroup: DepositGroupRecord = { contractTermsHash, - contractTermsRaw: contractTerms, depositGroupId, + currency: Amounts.currencyOf(totalDepositCost), + amount: contractData.amount, noncePriv: noncePair.priv, noncePub: noncePair.pub, timestampCreated: AbsoluteTime.toPreciseTimestamp(now), @@ -1375,6 +1388,7 @@ export async function createDepositGroup( counterpartyEffectiveDepositAmount: Amounts.stringify( counterpartyEffectiveDepositAmount, ), + wireTransferDeadline: contractTerms.wire_transfer_deadline, wire: { payto_uri: req.depositPaytoUri, salt: wireSalt, @@ -1395,6 +1409,7 @@ export async function createDepositGroup( x.denominations, x.refreshGroups, x.coinAvailability, + x.contractTerms, ]) .runReadWrite(async (tx) => { await spendCoins(ws, tx, { @@ -1406,6 +1421,10 @@ export async function createDepositGroup( refreshReason: RefreshReason.PayDeposit, }); await tx.depositGroups.put(depositGroup); + await tx.contractTerms.put({ + contractTermsRaw: contractTerms, + h: contractTermsHash, + }); return computeDepositTransactionStatus(depositGroup); }); diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts index f357c41d5..5bcfa3418 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts @@ -19,6 +19,7 @@ import { Amounts, CoinRefreshRequest, ConfirmPeerPullDebitRequest, + ContractTermsUtil, ExchangePurseDeposits, HttpStatusCode, Logger, @@ -103,9 +104,7 @@ async function handlePurseCreationConflict( throw new TalerProtocolViolationError(); } - const instructedAmount = Amounts.parseOrThrow( - peerPullInc.contractTerms.amount, - ); + const instructedAmount = Amounts.parseOrThrow(peerPullInc.amount); const sel = peerPullInc.coinSel; if (!sel) { @@ -142,9 +141,7 @@ async function handlePurseCreationConflict( await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const myPpi = await tx.peerPullDebit.get( - peerPullInc.peerPullDebitId, - ); + const myPpi = await tx.peerPullDebit.get(peerPullInc.peerPullDebitId); if (!myPpi) { return; } @@ -220,9 +217,7 @@ async function processPeerPullDebitPendingDeposit( const transitionInfo = await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const pi = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pi = await tx.peerPullDebit.get(peerPullDebitId); if (!pi) { throw Error("peer pull payment not found anymore"); } @@ -248,9 +243,7 @@ async function processPeerPullDebitPendingDeposit( x.coins, ]) .runReadWrite(async (tx) => { - const pi = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pi = await tx.peerPullDebit.get(peerPullDebitId); if (!pi) { throw Error("peer pull payment not found anymore"); } @@ -335,9 +328,7 @@ async function processPeerPullDebitAbortingRefresh( } } if (newOpState) { - const newDg = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const newDg = await tx.peerPullDebit.get(peerPullDebitId); if (!newDg) { return; } @@ -391,9 +382,7 @@ export async function confirmPeerPullDebit( } else if (req.peerPullDebitId) { peerPullDebitId = req.peerPullDebitId; } else { - throw Error( - "invalid request, transactionId or peerPullDebitId required", - ); + throw Error("invalid request, transactionId or peerPullDebitId required"); } const peerPullInc = await ws.db @@ -408,9 +397,7 @@ export async function confirmPeerPullDebit( ); } - const instructedAmount = Amounts.parseOrThrow( - peerPullInc.contractTerms.amount, - ); + const instructedAmount = Amounts.parseOrThrow(peerPullInc.amount); const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`); @@ -454,9 +441,7 @@ export async function confirmPeerPullDebit( refreshReason: RefreshReason.PayPeerPull, }); - const pi = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pi = await tx.peerPullDebit.get(peerPullDebitId); if (!pi) { throw Error(); } @@ -498,27 +483,36 @@ export async function preparePeerPullDebit( throw Error("got invalid taler://pay-pull URI"); } - const existingPullIncomingRecord = await ws.db - .mktx((x) => [x.peerPullDebit]) + const existing = await ws.db + .mktx((x) => [x.peerPullDebit, x.contractTerms]) .runReadOnly(async (tx) => { - return tx.peerPullDebit.indexes.byExchangeAndContractPriv.get([ - uri.exchangeBaseUrl, - uri.contractPriv, - ]); + const peerPullDebitRecord = + await tx.peerPullDebit.indexes.byExchangeAndContractPriv.get([ + uri.exchangeBaseUrl, + uri.contractPriv, + ]); + if (!peerPullDebitRecord) { + return; + } + const contractTerms = await tx.contractTerms.get( + peerPullDebitRecord.contractTermsHash, + ); + if (!contractTerms) { + return; + } + return { peerPullDebitRecord, contractTerms }; }); - if (existingPullIncomingRecord) { + if (existing) { return { - amount: existingPullIncomingRecord.contractTerms.amount, - amountRaw: existingPullIncomingRecord.contractTerms.amount, - amountEffective: existingPullIncomingRecord.totalCostEstimated, - contractTerms: existingPullIncomingRecord.contractTerms, - peerPullDebitId: - existingPullIncomingRecord.peerPullDebitId, + amount: existing.peerPullDebitRecord.amount, + amountRaw: existing.peerPullDebitRecord.amount, + amountEffective: existing.peerPullDebitRecord.totalCostEstimated, + contractTerms: existing.contractTerms.contractTermsRaw, + peerPullDebitId: existing.peerPullDebitRecord.peerPullDebitId, transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPullDebit, - peerPullDebitId: - existingPullIncomingRecord.peerPullDebitId, + peerPullDebitId: existing.peerPullDebitRecord.peerPullDebitId, }), }; } @@ -566,6 +560,8 @@ export async function preparePeerPullDebit( throw Error("pull payments without contract terms not supported yet"); } + const contractTermsHash = ContractTermsUtil.hashContractTerms(contractTerms); + // FIXME: Why don't we compute the totalCost here?! const instructedAmount = Amounts.parseOrThrow(contractTerms.amount); @@ -588,18 +584,23 @@ export async function preparePeerPullDebit( ); await ws.db - .mktx((x) => [x.peerPullDebit]) + .mktx((x) => [x.peerPullDebit, x.contractTerms]) .runReadWrite(async (tx) => { - await tx.peerPullDebit.add({ - peerPullDebitId, - contractPriv: contractPriv, - exchangeBaseUrl: exchangeBaseUrl, - pursePub: pursePub, - timestampCreated: TalerPreciseTimestamp.now(), - contractTerms, - status: PeerPullDebitRecordStatus.DialogProposed, - totalCostEstimated: Amounts.stringify(totalAmount), - }); + await tx.contractTerms.put({ + h: contractTermsHash, + contractTermsRaw: contractTerms, + }), + await tx.peerPullDebit.add({ + peerPullDebitId, + contractPriv: contractPriv, + exchangeBaseUrl: exchangeBaseUrl, + pursePub: pursePub, + timestampCreated: TalerPreciseTimestamp.now(), + contractTermsHash, + amount: contractTerms.amount, + status: PeerPullDebitRecordStatus.DialogProposed, + totalCostEstimated: Amounts.stringify(totalAmount), + }); }); return { @@ -631,9 +632,7 @@ export async function suspendPeerPullDebitTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const pullDebitRec = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); if (!pullDebitRec) { logger.warn(`peer pull debit ${peerPullDebitId} not found`); return; @@ -692,9 +691,7 @@ export async function abortPeerPullDebitTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const pullDebitRec = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); if (!pullDebitRec) { logger.warn(`peer pull debit ${peerPullDebitId} not found`); return; @@ -753,9 +750,7 @@ export async function failPeerPullDebitTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const pullDebitRec = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); if (!pullDebitRec) { logger.warn(`peer pull debit ${peerPullDebitId} not found`); return; @@ -814,9 +809,7 @@ export async function resumePeerPullDebitTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const pullDebitRec = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); if (!pullDebitRec) { logger.warn(`peer pull debit ${peerPullDebitId} not found`); return; diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 31655ad71..d7b277faf 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -346,11 +346,19 @@ export async function getTransactionById( } case TransactionType.PeerPullDebit: { return await ws.db - .mktx((x) => [x.peerPullDebit]) + .mktx((x) => [x.peerPullDebit, x.contractTerms]) .runReadWrite(async (tx) => { const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId); if (!debit) throw Error("not found"); - return buildTransactionForPullPaymentDebit(debit); + const contractTermsRec = await tx.contractTerms.get( + debit.contractTermsHash, + ); + if (!contractTermsRec) + throw Error("contract terms for peer-pull-debit not found"); + return buildTransactionForPullPaymentDebit( + debit, + contractTermsRec.contractTermsRaw, + ); }); } @@ -477,6 +485,7 @@ function buildTransactionForPushPaymentDebit( function buildTransactionForPullPaymentDebit( pi: PeerPullPaymentIncomingRecord, + contractTerms: PeerContractTerms, ort?: OperationRetryRecord, ): Transaction { return { @@ -485,12 +494,12 @@ function buildTransactionForPullPaymentDebit( txActions: computePeerPullDebitTransactionActions(pi), amountEffective: pi.coinSel?.totalCost ? pi.coinSel?.totalCost - : Amounts.stringify(pi.contractTerms.amount), - amountRaw: Amounts.stringify(pi.contractTerms.amount), + : Amounts.stringify(pi.amount), + amountRaw: Amounts.stringify(pi.amount), exchangeBaseUrl: pi.exchangeBaseUrl, info: { - expiration: pi.contractTerms.purse_expiration, - summary: pi.contractTerms.summary, + expiration: contractTerms.purse_expiration, + summary: contractTerms.summary, }, timestamp: pi.timestampCreated, transactionId: constructTransactionIdentifier({ @@ -805,7 +814,7 @@ function buildTransactionForDeposit( amountEffective: Amounts.stringify(dg.totalPayCost), timestamp: dg.timestampCreated, targetPaytoUri: dg.wire.payto_uri, - wireTransferDeadline: dg.contractTermsRaw.wire_transfer_deadline, + wireTransferDeadline: dg.wireTransferDeadline, transactionId: constructTransactionIdentifier({ tag: TransactionType.Deposit, depositGroupId: dg.depositGroupId, @@ -980,7 +989,7 @@ export async function getTransactions( }); await iterRecordsForPeerPullDebit(tx, filter, async (pi) => { - const amount = Amounts.parseOrThrow(pi.contractTerms.amount); + const amount = Amounts.parseOrThrow(pi.amount); if (shouldSkipCurrency(transactionsRequest, amount.currency)) { return; } @@ -991,10 +1000,23 @@ export async function getTransactions( pi.status !== PeerPullDebitRecordStatus.PendingDeposit && pi.status !== PeerPullDebitRecordStatus.Done ) { + // FIXME: Why?! return; } - transactions.push(buildTransactionForPullPaymentDebit(pi)); + const contractTermsRec = await tx.contractTerms.get( + pi.contractTermsHash, + ); + if (!contractTermsRec) { + return; + } + + transactions.push( + buildTransactionForPullPaymentDebit( + pi, + contractTermsRec.contractTermsRaw, + ), + ); }); await iterRecordsForPeerPushCredit(tx, filter, async (pi) => { @@ -1158,7 +1180,7 @@ export async function getTransactions( }); await iterRecordsForDeposit(tx, filter, async (dg) => { - const amount = Amounts.parseOrThrow(dg.contractTermsRaw.amount); + const amount = Amounts.parseOrThrow(dg.amount); if (shouldSkipCurrency(transactionsRequest, amount.currency)) { return; }