diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 9aedb888b..679ca2842 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -101,6 +101,14 @@ import { RetryInfo, TaskIdentifiers } from "./operations/common.js"; * @author Florian Dold */ +/** + FIXMEs: + - Contract terms can be quite large. We currently tend to read the + full contract terms from the DB quite often. + Instead, we should probably extract what we need into a separate object + store. + */ + /** * Name of the Taler database. This is effectively the major * version of the DB schema. Whenever it changes, custom import logic @@ -166,47 +174,47 @@ export enum WithdrawalGroupStatus { /** * Reserve must be registered with the bank. */ - PendingRegisteringBank = 0x0100_0000, - SuspendedRegisteringBank = 0x0110_0000, + PendingRegisteringBank = 0x0100_0001, + SuspendedRegisteringBank = 0x0110_0001, /** * We've registered reserve's information with the bank * and are now waiting for the user to confirm the withdraw * with the bank (typically 2nd factor auth). */ - PendingWaitConfirmBank = 0x0100_0001, - SuspendedWaitConfirmBank = 0x0110_0001, + PendingWaitConfirmBank = 0x0100_0002, + SuspendedWaitConfirmBank = 0x0110_0002, /** * Querying reserve status with the exchange. */ - PendingQueryingStatus = 0x0100_0002, - SuspendedQueryingStatus = 0x0110_0002, + PendingQueryingStatus = 0x0100_0003, + SuspendedQueryingStatus = 0x0110_0003, /** * Ready for withdrawal. */ - PendingReady = 0x0100_0003, - SuspendedReady = 0x0110_0003, + PendingReady = 0x0100_0004, + SuspendedReady = 0x0110_0004, /** * We are telling the bank that we don't want to complete * the withdrawal! */ - AbortingBank = 0x0103_0000, - SuspendedAbortingBank = 0x0113_0000, + AbortingBank = 0x0103_0001, + SuspendedAbortingBank = 0x0113_0001, /** * Exchange wants KYC info from the user. */ - PendingKyc = 0x0100_0004, - SuspendedKyc = 0x0110_004, + PendingKyc = 0x0100_0005, + SuspendedKyc = 0x0110_005, /** * Exchange is doing AML checks. */ - PendingAml = 0x0100_0005, - SuspendedAml = 0x0100_0005, + PendingAml = 0x0100_0006, + SuspendedAml = 0x0100_0006, /** * The corresponding withdraw record has been created. @@ -218,9 +226,9 @@ export enum WithdrawalGroupStatus { /** * The bank aborted the withdrawal. */ - FailedBankAborted = 0x0501_0000, + FailedBankAborted = 0x0501_0001, - FailedAbortingBank = 0x0501_0001, + FailedAbortingBank = 0x0501_0002, /** * Aborted in a state where we were supposed to @@ -272,9 +280,9 @@ export interface ReserveBankInfo { */ export enum DenominationVerificationStatus { /** - * Verification was delayed. + * Verification was delayed (pending). */ - Unverified = 0x0500_0000, + Unverified = 0x0100_0000, /** * Verified as valid. @@ -532,6 +540,7 @@ export enum ExchangeEntryDbRecordStatus { Used = 3, } +// FIXME: Use status ranges for this as well? export enum ExchangeEntryDbUpdateStatus { Initial = 1, InitialUpdate = 2, @@ -612,9 +621,9 @@ export interface ExchangeEntryRecord { } export enum PlanchetStatus { - Pending = 10 /* ACTIVE_START */, - KycRequired = 11 /* ACTIVE_START + 1 */, - WithdrawalDone = 50 /* DORMANT_START */, + Pending = 0x0100_0000, + KycRequired = 0x0100_0001, + WithdrawalDone = 0x0500_000, } /** @@ -1358,7 +1367,8 @@ export interface WgInfoBankManual { export interface WgInfoBankPeerPull { withdrawalType: WithdrawalRecordType.PeerPullCredit; - contractTerms: any; + // FIXME: include a transaction ID here? + /** * Needed to quickly construct the taler:// URI for the counterparty * without a join. @@ -1369,7 +1379,7 @@ export interface WgInfoBankPeerPull { export interface WgInfoBankPeerPush { withdrawalType: WithdrawalRecordType.PeerPushCredit; - contractTerms: any; + // FIXME: include a transaction ID here? } export interface WgInfoBankRecoup { @@ -1829,11 +1839,6 @@ export interface PeerPushDebitRecord { */ contractEncNonce: string; - /** - * FIXME: Put those in a different object store! - */ - contractTerms: PeerContractTerms; - purseExpiration: TalerProtocolTimestamp; timestampCreated: TalerPreciseTimestamp; @@ -1861,7 +1866,7 @@ export enum PeerPullPaymentCreditStatus { SuspendedCreatePurse = 0x0110_0000, SuspendedReady = 0x0110_0001, SuspendedMergeKycRequired = 0x0110_0002, - SuspendedWithdrawing = 0x0113_0000, + SuspendedWithdrawing = 0x0110_0000, SuspendedAbortingDeletePurse = 0x0113_0000, @@ -1878,6 +1883,7 @@ export interface PeerPullCreditRecord { /** * Amount requested. + * FIXME: What type of instructed amount is i? */ amount: AmountString; @@ -1908,11 +1914,6 @@ export interface PeerPullCreditRecord { contractEncNonce: string; - /** - * FIXME: Put in separate object store! - */ - contractTerms: PeerContractTerms; - mergeTimestamp: TalerPreciseTimestamp; mergeReserveRowId: number; @@ -2529,8 +2530,7 @@ export const WalletStoresV1 = { byWithdrawalGroupId: describeIndex( "byWithdrawalGroupId", "withdrawalGroupId", - { - }, + {}, ), }, ), @@ -2571,8 +2571,7 @@ export const WalletStoresV1 = { }), { byProposalId: describeIndex("byProposalId", "proposalId"), - byStatus: describeIndex("byStatus", "status", { - }), + byStatus: describeIndex("byStatus", "status", {}), }, ), refundItems: describeStore( diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index edadad1fc..0355eb152 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -27,6 +27,7 @@ import { InitiatePeerPullCreditResponse, Logger, NotificationType, + PeerContractTerms, TalerErrorCode, TalerPreciseTimestamp, TalerProtocolTimestamp, @@ -143,7 +144,6 @@ async function queryPurseForPeerPullCredit( amount: Amounts.parseOrThrow(pullIni.amount), wgInfo: { withdrawalType: WithdrawalRecordType.PeerPullCredit, - contractTerms: pullIni.contractTerms, contractPriv: pullIni.contractPriv, }, forcedWithdrawalGroupId: pullIni.withdrawalGroupId, @@ -283,9 +283,7 @@ async function processPeerPullCreditAbortingDeletePurse( if (!ppiRec) { return undefined; } - if ( - ppiRec.status !== PeerPullPaymentCreditStatus.AbortingDeletePurse - ) { + if (ppiRec.status !== PeerPullPaymentCreditStatus.AbortingDeletePurse) { return undefined; } const oldTxState = computePeerPullCreditTransactionState(ppiRec); @@ -371,6 +369,18 @@ async function handlePeerPullCreditCreatePurse( throw Error("merge reserve for peer pull payment not found in database"); } + const contractTermsRecord = await ws.db + .mktx((x) => [x.contractTerms]) + .runReadOnly(async (tx) => { + return tx.contractTerms.get(pullIni.contractTermsHash); + }); + + if (!contractTermsRecord) { + throw Error("contract terms for peer pull payment not found in database"); + } + + const contractTerms: PeerContractTerms = contractTermsRecord.contractTermsRaw; + const reservePayto = talerPaytoFromExchangeReserve( pullIni.exchangeBaseUrl, mergeReserve.reservePub, @@ -379,19 +389,19 @@ async function handlePeerPullCreditCreatePurse( const econtractResp = await ws.cryptoApi.encryptContractForDeposit({ contractPriv: pullIni.contractPriv, contractPub: pullIni.contractPub, - contractTerms: pullIni.contractTerms, + contractTerms: contractTermsRecord, pursePriv: pullIni.pursePriv, pursePub: pullIni.pursePub, nonce: pullIni.contractEncNonce, }); - const purseExpiration = pullIni.contractTerms.purse_expiration; + const purseExpiration = contractTerms.purse_expiration; const sigRes = await ws.cryptoApi.signReservePurseCreate({ contractTermsHash: pullIni.contractTermsHash, flags: WalletAccountMergeFlags.CreateWithPurseFee, mergePriv: pullIni.mergePriv, mergeTimestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp), - purseAmount: pullIni.contractTerms.amount, + purseAmount: pullIni.amount, purseExpiration: purseExpiration, purseFee: purseFee, pursePriv: pullIni.pursePriv, @@ -410,7 +420,7 @@ async function handlePeerPullCreditCreatePurse( purse_fee: purseFee, purse_pub: pullIni.pursePub, purse_sig: sigRes.purseSig, - purse_value: pullIni.contractTerms.amount, + purse_value: pullIni.amount, reserve_sig: sigRes.accountSig, econtract: econtractResp.econtract, }; @@ -585,8 +595,7 @@ async function processPeerPullCreditKycRequired( requirementRow: kycPending.requirement_row, }; peerInc.kycUrl = kycStatus.kyc_url; - peerInc.status = - PeerPullPaymentCreditStatus.PendingMergeKycRequired; + peerInc.status = PeerPullPaymentCreditStatus.PendingMergeKycRequired; const newTxState = computePeerPullCreditTransactionState(peerInc); await tx.peerPullCredit.put(peerInc); // We'll remove this eventually! New clients should rely on the @@ -769,7 +778,6 @@ export async function initiatePeerPullPayment( mergePriv: mergePair.priv, mergePub: mergePair.pub, status: PeerPullPaymentCreditStatus.PendingCreatePurse, - contractTerms: contractTerms, mergeTimestamp, contractEncNonce, mergeReserveRowId: mergeReserveRowId, @@ -848,8 +856,7 @@ export async function suspendPeerPullCreditTransaction( newStatus = PeerPullPaymentCreditStatus.SuspendedReady; break; case PeerPullPaymentCreditStatus.AbortingDeletePurse: - newStatus = - PeerPullPaymentCreditStatus.SuspendedAbortingDeletePurse; + newStatus = PeerPullPaymentCreditStatus.SuspendedAbortingDeletePurse; break; case PeerPullPaymentCreditStatus.Done: case PeerPullPaymentCreditStatus.SuspendedCreatePurse: diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index f0f659aa3..89d9e3b49 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -446,7 +446,6 @@ async function handlePendingMerge( amount, wgInfo: { withdrawalType: WithdrawalRecordType.PeerPushCredit, - contractTerms, }, forcedWithdrawalGroupId: peerInc.withdrawalGroupId, exchangeBaseUrl: peerInc.exchangeBaseUrl, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index a2e7a6891..e80ffc059 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -154,9 +154,7 @@ async function handlePurseCreationConflict( await ws.db .mktx((x) => [x.peerPushDebit]) .runReadWrite(async (tx) => { - const myPpi = await tx.peerPushDebit.get( - peerPushInitiation.pursePub, - ); + const myPpi = await tx.peerPushDebit.get(peerPushInitiation.pursePub); if (!myPpi) { return; } @@ -182,10 +180,27 @@ async function processPeerPushDebitCreateReserve( ws: InternalWalletState, peerPushInitiation: PeerPushDebitRecord, ): Promise { - logger.info("processing peer-push-debit pending(create-reserve)"); const pursePub = peerPushInitiation.pursePub; const purseExpiration = peerPushInitiation.purseExpiration; const hContractTerms = peerPushInitiation.contractTermsHash; + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: pursePub, + }); + + logger.trace(`processing ${transactionId} pending(create-reserve)`); + + const contractTermsRecord = await ws.db + .mktx((x) => [x.contractTerms]) + .runReadOnly(async (tx) => { + return tx.contractTerms.get(hContractTerms); + }); + + if (!contractTermsRecord) { + throw Error( + `db invariant failed, contract terms for ${transactionId} missing`, + ); + } const purseSigResp = await ws.cryptoApi.signPurseCreation({ hContractTerms, @@ -208,7 +223,7 @@ async function processPeerPushDebitCreateReserve( }); const encryptContractRequest: EncryptContractRequest = { - contractTerms: peerPushInitiation.contractTerms, + contractTerms: contractTermsRecord.contractTermsRaw, mergePriv: peerPushInitiation.mergePriv, pursePriv: peerPushInitiation.pursePriv, pursePub: peerPushInitiation.pursePub, @@ -329,9 +344,7 @@ async function processPeerPushDebitAbortingDeletePurse( if (!ppiRec) { return undefined; } - if ( - ppiRec.status !== PeerPushDebitStatus.AbortingDeletePurse - ) { + if (ppiRec.status !== PeerPushDebitStatus.AbortingDeletePurse) { return undefined; } const currency = Amounts.currencyOf(ppiRec.amount); @@ -658,7 +671,6 @@ export async function initiatePeerPushDebit( pursePub: pursePair.pub, timestampCreated: TalerPreciseTimestamp.now(), status: PeerPushDebitStatus.PendingCreatePurse, - contractTerms: contractTerms, contractEncNonce, coinSel: { coinPubs: sel.coins.map((x) => x.coinPub), @@ -881,8 +893,7 @@ export async function suspendPeerPushDebitTransaction( newStatus = PeerPushDebitStatus.SuspendedAbortingRefresh; break; case PeerPushDebitStatus.AbortingDeletePurse: - newStatus = - PeerPushDebitStatus.SuspendedAbortingDeletePurse; + newStatus = PeerPushDebitStatus.SuspendedAbortingDeletePurse; break; case PeerPushDebitStatus.PendingReady: newStatus = PeerPushDebitStatus.SuspendedReady; diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 5c57195c1..ff9fbf57a 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -154,7 +154,18 @@ import { resumePeerPushDebitTransaction, abortPeerPushDebitTransaction, } from "./pay-peer-push-debit.js"; -import { iterRecordsForDeposit, iterRecordsForPeerPullDebit, iterRecordsForPeerPullInitiation, iterRecordsForPeerPushCredit, iterRecordsForPeerPushInitiation, iterRecordsForPurchase, iterRecordsForRefresh, iterRecordsForRefund, iterRecordsForReward, iterRecordsForWithdrawal } from "./pending.js"; +import { + iterRecordsForDeposit, + iterRecordsForPeerPullDebit, + iterRecordsForPeerPullInitiation, + iterRecordsForPeerPushCredit, + iterRecordsForPeerPushInitiation, + iterRecordsForPurchase, + iterRecordsForRefresh, + iterRecordsForRefund, + iterRecordsForReward, + iterRecordsForWithdrawal, +} from "./pending.js"; const logger = new Logger("taler-wallet-core:transactions.ts"); @@ -337,9 +348,7 @@ export async function getTransactionById( return await ws.db .mktx((x) => [x.peerPullDebit]) .runReadWrite(async (tx) => { - const debit = await tx.peerPullDebit.get( - parsedTx.peerPullDebitId, - ); + const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId); if (!debit) throw Error("not found"); return buildTransactionForPullPaymentDebit(debit); }); @@ -349,9 +358,7 @@ export async function getTransactionById( return await ws.db .mktx((x) => [x.peerPushDebit, x.contractTerms]) .runReadWrite(async (tx) => { - const debit = await tx.peerPushDebit.get( - parsedTx.pursePub, - ); + const debit = await tx.peerPushDebit.get(parsedTx.pursePub); if (!debit) throw Error("not found"); const ct = await tx.contractTerms.get(debit.contractTermsHash); checkDbInvariant(!!ct); @@ -372,9 +379,7 @@ export async function getTransactionById( x.operationRetries, ]) .runReadWrite(async (tx) => { - const pushInc = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); checkDbInvariant(!!ct); @@ -532,8 +537,8 @@ function buildTransactionForPeerPullCredit( // Old transactions don't have it! timestamp: pullCredit.mergeTimestamp ?? TalerPreciseTimestamp.now(), info: { - expiration: wsr.wgInfo.contractTerms.purse_expiration, - summary: wsr.wgInfo.contractTerms.summary, + expiration: peerContractTerms.purse_expiration, + summary: peerContractTerms.summary, }, talerUri: stringifyPayPullUri({ exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -600,8 +605,8 @@ function buildTransactionForPeerPushCredit( amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, info: { - expiration: wsr.wgInfo.contractTerms.purse_expiration, - summary: wsr.wgInfo.contractTerms.summary, + expiration: peerContractTerms.purse_expiration, + summary: peerContractTerms.summary, }, timestamp: wsr.timestampStart, transactionId: constructTransactionIdentifier({ @@ -1033,7 +1038,7 @@ export async function getTransactions( ), ); }); - + await iterRecordsForPeerPullInitiation(tx, filter, async (pi) => { const currency = Amounts.currencyOf(pi.amount); if (shouldSkipCurrency(transactionsRequest, currency)) { @@ -1078,7 +1083,7 @@ export async function getTransactions( ); transactions.push(buildTransactionForRefund(refundGroup, contractData)); }); - + await iterRecordsForRefresh(tx, filter, async (rg) => { if (shouldSkipCurrency(transactionsRequest, rg.currency)) { return; @@ -1099,7 +1104,7 @@ export async function getTransactions( } }); - await iterRecordsForWithdrawal(tx, filter ,async (wsr) => { + await iterRecordsForWithdrawal(tx, filter, async (wsr) => { if ( shouldSkipCurrency( transactionsRequest, @@ -1643,15 +1648,9 @@ export async function deleteTransaction( case TransactionType.PeerPushCredit: { const peerPushCreditId = parsedTx.peerPushCreditId; await ws.db - .mktx((x) => [ - x.withdrawalGroups, - x.peerPushCredit, - x.tombstones, - ]) + .mktx((x) => [x.withdrawalGroups, x.peerPushCredit, x.tombstones]) .runReadWrite(async (tx) => { - const pushInc = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) { return; } @@ -1670,10 +1669,7 @@ export async function deleteTransaction( } await tx.peerPushCredit.delete(peerPushCreditId); await tx.tombstones.put({ - id: - TombstoneTag.DeletePeerPushCredit + - ":" + - peerPushCreditId, + id: TombstoneTag.DeletePeerPushCredit + ":" + peerPushCreditId, }); }); return; @@ -1682,11 +1678,7 @@ export async function deleteTransaction( case TransactionType.PeerPullCredit: { const pursePub = parsedTx.pursePub; await ws.db - .mktx((x) => [ - x.withdrawalGroups, - x.peerPullCredit, - x.tombstones, - ]) + .mktx((x) => [x.withdrawalGroups, x.peerPullCredit, x.tombstones]) .runReadWrite(async (tx) => { const pullIni = await tx.peerPullCredit.get(pursePub); if (!pullIni) { @@ -1813,9 +1805,7 @@ export async function deleteTransaction( await ws.db .mktx((x) => [x.peerPullDebit, x.tombstones]) .runReadWrite(async (tx) => { - const debit = await tx.peerPullDebit.get( - peerPullDebitId, - ); + const debit = await tx.peerPullDebit.get(peerPullDebitId); if (debit) { await tx.peerPullDebit.delete(peerPullDebitId); await tx.tombstones.put({ id: transactionId });