From a66b636dee2ed531bb5119feced80d6569d99176 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 14 Sep 2022 21:27:03 +0200 Subject: wallet-core: restructure denomination record for easier querying --- .../src/crypto/cryptoImplementation.ts | 15 +- packages/taler-wallet-core/src/db.ts | 96 ++++--- packages/taler-wallet-core/src/dbless.ts | 28 +- .../src/operations/backup/export.ts | 11 +- .../src/operations/backup/import.ts | 45 +++- .../taler-wallet-core/src/operations/deposits.ts | 6 +- .../taler-wallet-core/src/operations/exchanges.ts | 15 +- packages/taler-wallet-core/src/operations/pay.ts | 23 +- .../taler-wallet-core/src/operations/refresh.ts | 6 +- .../taler-wallet-core/src/operations/refund.ts | 23 +- packages/taler-wallet-core/src/operations/tip.ts | 2 +- .../src/operations/withdraw.test.ts | 288 ++++++++++----------- .../taler-wallet-core/src/operations/withdraw.ts | 30 ++- packages/taler-wallet-core/src/wallet.ts | 49 +++- 14 files changed, 390 insertions(+), 247 deletions(-) diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index 4ec24a98b..9eaf1d91e 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -892,17 +892,22 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { req: DenominationValidationRequest, ): Promise { const { masterPub, denom } = req; + const value: AmountJson = { + currency: denom.currency, + fraction: denom.amountFrac, + value: denom.amountVal, + }; const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) .put(decodeCrock(masterPub)) .put(timestampRoundedToBuffer(denom.stampStart)) .put(timestampRoundedToBuffer(denom.stampExpireWithdraw)) .put(timestampRoundedToBuffer(denom.stampExpireDeposit)) .put(timestampRoundedToBuffer(denom.stampExpireLegal)) - .put(amountToBuffer(denom.value)) - .put(amountToBuffer(denom.feeWithdraw)) - .put(amountToBuffer(denom.feeDeposit)) - .put(amountToBuffer(denom.feeRefresh)) - .put(amountToBuffer(denom.feeRefund)) + .put(amountToBuffer(value)) + .put(amountToBuffer(denom.fees.feeWithdraw)) + .put(amountToBuffer(denom.fees.feeDeposit)) + .put(amountToBuffer(denom.fees.feeRefresh)) + .put(amountToBuffer(denom.fees.feeRefund)) .put(decodeCrock(denom.denomPubHash)) .build(); const sig = decodeCrock(denom.masterSig); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 6dfa63c06..2c4d55820 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -29,7 +29,6 @@ import { CoinDepositPermission, ContractTerms, DenominationPubKey, - Duration, ExchangeSignKeyJson, InternationalizedString, MerchantInfo, @@ -48,6 +47,7 @@ import { } from "@gnu-taler/taler-util"; import { RetryInfo } from "./util/retries.js"; import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; +import { DenomInfo } from "./internal-wallet-state.js"; /** * Name of the Taler database. This is effectively the major @@ -213,26 +213,7 @@ export enum DenominationVerificationStatus { VerifiedBad = "verified-bad", } -/** - * Denomination record as stored in the wallet's database. - */ -export interface DenominationRecord { - /** - * Value of one coin of the denomination. - */ - value: AmountJson; - - /** - * The denomination public key. - */ - denomPub: DenominationPubKey; - - /** - * Hash of the denomination public key. - * Stored in the database for faster lookups. - */ - denomPubHash: string; - +export interface DenomFees { /** * Fee for withdrawing. */ @@ -252,6 +233,30 @@ export interface DenominationRecord { * Fee for refunding. */ feeRefund: AmountJson; +} + +/** + * Denomination record as stored in the wallet's database. + */ +export interface DenominationRecord { + currency: string; + + amountVal: number; + + amountFrac: number; + + /** + * The denomination public key. + */ + denomPub: DenominationPubKey; + + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + denomPubHash: string; + + fees: DenomFees; /** * Validity start date of the denomination. @@ -321,6 +326,32 @@ export interface DenominationRecord { freshCoinCount?: number; } +export namespace DenominationRecord { + export function getValue(d: DenominationRecord): AmountJson { + return { + currency: d.currency, + fraction: d.amountFrac, + value: d.amountVal, + }; + } + + export function toDenomInfo(d: DenominationRecord): DenomInfo { + return { + denomPub: d.denomPub, + denomPubHash: d.denomPubHash, + feeDeposit: d.fees.feeDeposit, + feeRefresh: d.fees.feeRefresh, + feeRefund: d.fees.feeRefund, + feeWithdraw: d.fees.feeWithdraw, + stampExpireDeposit: d.stampExpireDeposit, + stampExpireLegal: d.stampExpireLegal, + stampExpireWithdraw: d.stampExpireWithdraw, + stampStart: d.stampStart, + value: DenominationRecord.getValue(d), + }; + } +} + /** * Information about one of the exchange's bank accounts. */ @@ -2031,7 +2062,10 @@ export interface DatabaseDump { version: string; } -async function recoverFromDump(db: IDBDatabase, dump: DatabaseDump): Promise { +async function recoverFromDump( + db: IDBDatabase, + dump: DatabaseDump, +): Promise { return new Promise((resolve, reject) => { const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); tx.addEventListener("complete", () => { @@ -2048,13 +2082,13 @@ async function recoverFromDump(db: IDBDatabase, dump: DatabaseDump): Promise { if ("name" in object && "stores" in object && "version" in object) { // looks like a database dump - const dump = object as DatabaseDump + const dump = object as DatabaseDump; return recoverFromDump(db, dump); } @@ -2064,10 +2098,12 @@ export async function importDb(db: IDBDatabase, object: any): Promise { if (TALER_META_DB_NAME in someDatabase) { //looks like a taler database - const currentMainDbValue = someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0].value.value + const currentMainDbValue = + someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0] + .value.value; if (currentMainDbValue !== TALER_DB_NAME) { - console.log("not the current database version") + console.log("not the current database version"); } const talerDb = someDatabase[currentMainDbValue]; @@ -2078,17 +2114,17 @@ export async function importDb(db: IDBDatabase, object: any): Promise { name: talerDb.schema.databaseName, version: talerDb.schema.databaseVersion, stores: {}, - } + }; for (let i = 0; i < objectStoreNames.length; i++) { const name = objectStoreNames[i]; const storeDump = {} as { [s: string]: any }; dump.stores[name] = storeDump; talerDb.objectStores[name].records.map((r: any) => { - const pkey = r.primaryKey - const key = typeof pkey === "string" ? pkey : pkey.join(",") + const pkey = r.primaryKey; + const key = typeof pkey === "string" ? pkey : pkey.join(","); storeDump[key] = r.value; - }) + }); } return recoverFromDump(db, dump); diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 3a775c3f1..652ba8f53 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -48,6 +48,7 @@ import { UnblindedSignature, BankWithdrawDetails, parseWithdrawUri, + AmountJson, } from "@gnu-taler/taler-util"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { DenominationRecord } from "./db.js"; @@ -158,11 +159,15 @@ export async function withdrawCoin(args: { const planchet = await cryptoApi.createPlanchet({ coinIndex: 0, denomPub: denom.denomPub, - feeWithdraw: denom.feeWithdraw, + feeWithdraw: denom.fees.feeWithdraw, reservePriv: reserveKeyPair.reservePriv, reservePub: reserveKeyPair.reservePub, secretSeed: encodeCrock(getRandomBytes(32)), - value: denom.value, + value: { + currency: denom.currency, + fraction: denom.amountFrac, + value: denom.amountVal, + }, }); const reqBody: ExchangeWithdrawRequest = { @@ -192,8 +197,8 @@ export async function withdrawCoin(args: { denomSig: ubSig, denomPub: denom.denomPub, denomPubHash: denom.denomPubHash, - feeDeposit: Amounts.stringify(denom.feeDeposit), - feeRefresh: Amounts.stringify(denom.feeRefresh), + feeDeposit: Amounts.stringify(denom.fees.feeDeposit), + feeRefresh: Amounts.stringify(denom.fees.feeRefresh), exchangeBaseUrl: args.exchangeBaseUrl, }; } @@ -203,7 +208,12 @@ export function findDenomOrThrow( amount: AmountString, ): DenominationRecord { for (const d of exchangeInfo.keys.currentDenominations) { - if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) { + const value: AmountJson = { + currency: d.currency, + fraction: d.amountFrac, + value: d.amountVal, + }; + if (Amounts.cmp(value, amount) === 0 && isWithdrawableDenom(d)) { return d; } } @@ -281,8 +291,12 @@ export async function refreshCoin(req: { count: 1, denomPub: x.denomPub, denomPubHash: x.denomPubHash, - feeWithdraw: x.feeWithdraw, - value: x.value, + feeWithdraw: x.fees.feeWithdraw, + value: { + currency: x.currency, + fraction: x.amountFrac, + value: x.amountVal, + }, })), }); diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index fb1fbf90b..35d5e6ef7 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -62,6 +62,7 @@ import { AbortStatus, CoinSourceType, CoinStatus, + DenominationRecord, ProposalStatus, RefreshCoinStatus, RefundState, @@ -221,10 +222,10 @@ export async function exportBackup( backupDenoms.push({ coins: backupCoinsByDenom[denom.denomPubHash] ?? [], denom_pub: denom.denomPub, - fee_deposit: Amounts.stringify(denom.feeDeposit), - fee_refresh: Amounts.stringify(denom.feeRefresh), - fee_refund: Amounts.stringify(denom.feeRefund), - fee_withdraw: Amounts.stringify(denom.feeWithdraw), + fee_deposit: Amounts.stringify(denom.fees.feeDeposit), + fee_refresh: Amounts.stringify(denom.fees.feeRefresh), + fee_refund: Amounts.stringify(denom.fees.feeRefund), + fee_withdraw: Amounts.stringify(denom.fees.feeWithdraw), is_offered: denom.isOffered, is_revoked: denom.isRevoked, master_sig: denom.masterSig, @@ -232,7 +233,7 @@ export async function exportBackup( stamp_expire_legal: denom.stampExpireLegal, stamp_expire_withdraw: denom.stampExpireWithdraw, stamp_start: denom.stampStart, - value: Amounts.stringify(denom.value), + value: Amounts.stringify(DenominationRecord.getValue(denom)), list_issue_date: denom.listIssueDate, }); }); diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 53e45918e..53dc50f3b 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -38,6 +38,7 @@ import { CoinSource, CoinSourceType, CoinStatus, + DenominationRecord, DenominationVerificationStatus, DenomSelectionState, OperationStatus, @@ -108,7 +109,10 @@ async function recoverPayCoinSelection( coinRecord.denomPubHash, ]); checkBackupInvariant(!!denom); - totalDepositFees = Amounts.add(totalDepositFees, denom.feeDeposit).amount; + totalDepositFees = Amounts.add( + totalDepositFees, + denom.fees.feeDeposit, + ).amount; if (!coveredExchanges.has(coinRecord.exchangeBaseUrl)) { const exchangeDetails = await getExchangeDetails( @@ -175,16 +179,19 @@ async function getDenomSelStateFromBackup( denomPubHash: string; count: number; }[] = []; - let totalCoinValue = Amounts.getZero(d0.value.currency); - let totalWithdrawCost = Amounts.getZero(d0.value.currency); + let totalCoinValue = Amounts.getZero(d0.currency); + let totalWithdrawCost = Amounts.getZero(d0.currency); for (const s of sel) { const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]); checkBackupInvariant(!!d); - totalCoinValue = Amounts.add(totalCoinValue, d.value).amount; + totalCoinValue = Amounts.add( + totalCoinValue, + DenominationRecord.getValue(d), + ).amount; totalWithdrawCost = Amounts.add( totalWithdrawCost, - d.value, - d.feeWithdraw, + DenominationRecord.getValue(d), + d.fees.feeWithdraw, ).amount; } return { @@ -352,17 +359,25 @@ export async function importBackup( `importing backup denomination: ${j2s(backupDenomination)}`, ); + const value = Amounts.parseOrThrow(backupDenomination.value); + await tx.denominations.put({ denomPub: backupDenomination.denom_pub, denomPubHash: denomPubHash, exchangeBaseUrl: backupExchangeDetails.base_url, exchangeMasterPub: backupExchangeDetails.master_public_key, - feeDeposit: Amounts.parseOrThrow(backupDenomination.fee_deposit), - feeRefresh: Amounts.parseOrThrow(backupDenomination.fee_refresh), - feeRefund: Amounts.parseOrThrow(backupDenomination.fee_refund), - feeWithdraw: Amounts.parseOrThrow( - backupDenomination.fee_withdraw, - ), + fees: { + feeDeposit: Amounts.parseOrThrow( + backupDenomination.fee_deposit, + ), + feeRefresh: Amounts.parseOrThrow( + backupDenomination.fee_refresh, + ), + feeRefund: Amounts.parseOrThrow(backupDenomination.fee_refund), + feeWithdraw: Amounts.parseOrThrow( + backupDenomination.fee_withdraw, + ), + }, isOffered: backupDenomination.is_offered, isRevoked: backupDenomination.is_revoked, masterSig: backupDenomination.master_sig, @@ -371,7 +386,9 @@ export async function importBackup( stampExpireWithdraw: backupDenomination.stamp_expire_withdraw, stampStart: backupDenomination.stamp_start, verificationStatus: DenominationVerificationStatus.VerifiedGood, - value: Amounts.parseOrThrow(backupDenomination.value), + currency: value.currency, + amountFrac: value.fraction, + amountVal: value.value, listIssueDate: backupDenomination.list_issue_date, }); } @@ -648,7 +665,7 @@ export async function importBackup( executionTime: backupRefund.execution_time, obtainedTime: backupRefund.obtained_time, refundAmount: Amounts.parseOrThrow(backupRefund.refund_amount), - refundFee: denom.feeRefund, + refundFee: denom.fees.feeRefund, rtransactionId: backupRefund.rtransaction_id, totalRefreshCostBound: Amounts.parseOrThrow( backupRefund.total_refresh_cost_bound, diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 6d63def59..612de8240 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -44,6 +44,7 @@ import { URL, } from "@gnu-taler/taler-util"; import { + DenominationRecord, DepositGroupRecord, OperationAttemptResult, OperationStatus, @@ -636,7 +637,10 @@ export async function getTotalFeesForDepositAmount( const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl .iter(coin.exchangeBaseUrl) .filter((x) => - Amounts.isSameCurrency(x.value, pcs.coinContributions[i]), + Amounts.isSameCurrency( + DenominationRecord.getValue(x), + pcs.coinContributions[i], + ), ); const amountLeft = Amounts.sub( denom.value, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 504978441..ca85ff465 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -81,15 +81,18 @@ function denominationRecordFromKeys( let denomPub: DenominationPubKey; denomPub = denomIn.denom_pub; const denomPubHash = encodeCrock(hashDenomPub(denomPub)); + const value = Amounts.parseOrThrow(denomIn.value); const d: DenominationRecord = { denomPub, denomPubHash, exchangeBaseUrl, exchangeMasterPub, - feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit), - feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh), - feeRefund: Amounts.parseOrThrow(denomIn.fee_refund), - feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw), + fees: { + feeDeposit: Amounts.parseOrThrow(denomIn.fee_deposit), + feeRefresh: Amounts.parseOrThrow(denomIn.fee_refresh), + feeRefund: Amounts.parseOrThrow(denomIn.fee_refund), + feeWithdraw: Amounts.parseOrThrow(denomIn.fee_withdraw), + }, isOffered: true, isRevoked: false, masterSig: denomIn.master_sig, @@ -98,7 +101,9 @@ function denominationRecordFromKeys( stampExpireWithdraw: denomIn.stamp_expire_withdraw, stampStart: denomIn.stamp_start, verificationStatus: DenominationVerificationStatus.Unverified, - value: Amounts.parseOrThrow(denomIn.value), + amountFrac: value.fraction, + amountVal: value.value, + currency: value.currency, listIssueDate, }; return d; diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index bd7b1f7f0..5a0d3cee3 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -141,13 +141,20 @@ export async function getTotalPaymentCost( const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl .iter(coin.exchangeBaseUrl) .filter((x) => - Amounts.isSameCurrency(x.value, pcs.coinContributions[i]), + Amounts.isSameCurrency( + DenominationRecord.getValue(x), + pcs.coinContributions[i], + ), ); const amountLeft = Amounts.sub( - denom.value, + DenominationRecord.getValue(denom), pcs.coinContributions[i], ).amount; - const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft); + const refreshCost = getTotalRefreshCost( + allDenoms, + DenominationRecord.toDenomInfo(denom), + amountLeft, + ); costs.push(pcs.coinContributions[i]); costs.push(refreshCost); } @@ -303,7 +310,7 @@ export async function getCandidatePayCoins( if (!denom) { throw Error("db inconsistent"); } - if (denom.value.currency !== currency) { + if (denom.currency !== currency) { logger.warn( `same pubkey for different currencies at exchange ${exchange.baseUrl}`, ); @@ -314,10 +321,10 @@ export async function getCandidatePayCoins( } candidateCoins.push({ availableAmount: coin.currentAmount, - value: denom.value, + value: DenominationRecord.getValue(denom), coinPub: coin.coinPub, denomPub: denom.denomPub, - feeDeposit: denom.feeDeposit, + feeDeposit: denom.fees.feeDeposit, exchangeBaseUrl: denom.exchangeBaseUrl, ageCommitmentProof: coin.ageCommitmentProof, }); @@ -949,7 +956,7 @@ async function handleInsufficientFunds( coinPub, contribution: contrib, exchangeBaseUrl: coin.exchangeBaseUrl, - feeDeposit: denom.feeDeposit, + feeDeposit: denom.fees.feeDeposit, }); } }); @@ -1269,7 +1276,7 @@ export async function generateDepositPermissions( denomKeyType: denom.denomPub.cipher, denomSig: coin.denomSig, exchangeBaseUrl: coin.exchangeBaseUrl, - feeDeposit: denom.feeDeposit, + feeDeposit: denom.fees.feeDeposit, merchantPub: contractData.merchantPub, refundDeadline: contractData.refundDeadline, spendAmount: payCoinSel.coinContributions[i], diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index d1c366cd0..2d9ad2c05 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -112,7 +112,11 @@ export function getTotalRefreshCost( const resultingAmount = Amounts.add( Amounts.getZero(withdrawAmount.currency), ...withdrawDenoms.selectedDenoms.map( - (d) => Amounts.mult(denomMap[d.denomPubHash].value, d.count).amount, + (d) => + Amounts.mult( + DenominationRecord.getValue(denomMap[d.denomPubHash]), + d.count, + ).amount, ), ).amount; const totalCost = Amounts.sub(amountLeft, resultingAmount).amount; diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 5ee0680d7..f028dfbf1 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -51,6 +51,7 @@ import { import { AbortStatus, CoinStatus, + DenominationRecord, OperationAttemptResult, PurchaseRecord, RefundReason, @@ -148,7 +149,7 @@ async function applySuccessfulRefund( } refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub }; const refundAmount = Amounts.parseOrThrow(r.refund_amount); - const refundFee = denom.feeRefund; + const refundFee = denom.fees.feeRefund; coin.status = CoinStatus.Dormant; coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount; coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount; @@ -162,12 +163,12 @@ async function applySuccessfulRefund( const amountLeft = Amounts.sub( Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount)) .amount, - denom.feeRefund, + denom.fees.feeRefund, ).amount; const totalRefreshCostBound = getTotalRefreshCost( allDenoms, - denom, + DenominationRecord.toDenomInfo(denom), amountLeft, ); @@ -176,7 +177,7 @@ async function applySuccessfulRefund( obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), - refundFee: denom.feeRefund, + refundFee: denom.fees.feeRefund, totalRefreshCostBound, coinPub: r.coin_pub, rtransactionId: r.rtransaction_id, @@ -214,12 +215,12 @@ async function storePendingRefund( const amountLeft = Amounts.sub( Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount)) .amount, - denom.feeRefund, + denom.fees.feeRefund, ).amount; const totalRefreshCostBound = getTotalRefreshCost( allDenoms, - denom, + DenominationRecord.toDenomInfo(denom), amountLeft, ); @@ -228,7 +229,7 @@ async function storePendingRefund( obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), - refundFee: denom.feeRefund, + refundFee: denom.fees.feeRefund, totalRefreshCostBound, coinPub: r.coin_pub, rtransactionId: r.rtransaction_id, @@ -267,12 +268,12 @@ async function storeFailedRefund( const amountLeft = Amounts.sub( Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount)) .amount, - denom.feeRefund, + denom.fees.feeRefund, ).amount; const totalRefreshCostBound = getTotalRefreshCost( allDenoms, - denom, + DenominationRecord.toDenomInfo(denom), amountLeft, ); @@ -281,7 +282,7 @@ async function storeFailedRefund( obtainedTime: TalerProtocolTimestamp.now(), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), - refundFee: denom.feeRefund, + refundFee: denom.fees.feeRefund, totalRefreshCostBound, coinPub: r.coin_pub, rtransactionId: r.rtransaction_id, @@ -314,7 +315,7 @@ async function storeFailedRefund( coin.currentAmount = Amounts.add(coin.currentAmount, contrib).amount; coin.currentAmount = Amounts.sub( coin.currentAmount, - denom.feeRefund, + denom.fees.feeRefund, ).amount; } refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub }; diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index f70e2d02b..c8f327a56 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -306,7 +306,7 @@ export async function processTip( coinIndex: i, walletTipId: walletTipId, }, - currentAmount: denom.value, + currentAmount: DenominationRecord.getValue(denom), denomPubHash: denom.denomPubHash, denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig }, exchangeBaseUrl: tipRecord.exchangeBaseUrl, diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts index 9f9146719..70b4f73c0 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.test.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts @@ -38,25 +38,27 @@ test("withdrawal selection bug repro", (t) => { "Q21FQSSG4FXNT96Z14CHXM8N1RZAG9GPHAV8PRWS0PZAAVWH7PBW6R97M2CH19KKP65NNSWXY7B6S53PT3CBM342E357ZXDDJ8RDVW8", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -75,11 +77,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 0, - value: 1000, - }, + currency: "KUDOS", + amountFrac: 0, + amountVal: 1000, listIssueDate: { t_s: 0 }, }, { @@ -94,25 +94,27 @@ test("withdrawal selection bug repro", (t) => { "447WA23SCBATMABHA0793F92MYTBYVPYMMQHCPKMKVY5P7RZRFMQ6VRW0Y8HRA7177GTBT0TBT08R21DZD129AJ995H9G09XBFE55G8", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -131,11 +133,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 0, - value: 10, - }, + amountFrac: 0, + amountVal: 10, + currency: "KUDOS", listIssueDate: { t_s: 0 }, }, { @@ -149,25 +149,27 @@ test("withdrawal selection bug repro", (t) => { "JS61DTKAFM0BX8Q4XV3ZSKB921SM8QK745Z2AFXTKFMBHHFNBD8TQ5ETJHFNDGBGX22FFN2A2ERNYG1SGSDQWNQHQQ2B14DBVJYJG8R", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -186,11 +188,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 0, - value: 5, - }, + amountFrac: 0, + amountVal: 5, + currency: "KUDOS", listIssueDate: { t_s: 0 }, }, { @@ -205,25 +205,27 @@ test("withdrawal selection bug repro", (t) => { "8T51NEY81VMPQ180EQ5WR0YH7GMNNT90W55Q0514KZM18AZT71FHJGJHQXGK0WTA7ACN1X2SD0S53XPBQ1A9KH960R48VCVVM6E3TH8", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -242,11 +244,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 0, - value: 1, - }, + amountFrac: 0, + amountVal: 1, + currency: "KUDOS", listIssueDate: { t_s: 0 }, }, { @@ -260,25 +260,27 @@ test("withdrawal selection bug repro", (t) => { "A41HW0Q2H9PCNMEWW0C0N45QAYVXZ8SBVRRAHE4W6X24SV1TH38ANTWDT80JXEBW9Z8PVPGT9GFV2EYZWJ5JW5W1N34NFNKHQSZ1PFR", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -297,11 +299,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 10000000, - value: 0, - }, + amountFrac: 10000000, + amountVal: 0, + currency: "KUDOS", listIssueDate: { t_s: 0 }, }, { @@ -315,25 +315,27 @@ test("withdrawal selection bug repro", (t) => { "F5NGBX33DTV4595XZZVK0S2MA1VMXFEJQERE5EBP5DS4QQ9EFRANN7YHWC1TKSHT2K6CQWDBRES8D3DWR0KZF5RET40B4AZXZ0RW1ZG", exchangeBaseUrl: "https://exchange.demo.taler.net/", exchangeMasterPub: "", - feeDeposit: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefresh: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeRefund: { - currency: "KUDOS", - fraction: 1000000, - value: 0, - }, - feeWithdraw: { - currency: "KUDOS", - fraction: 1000000, - value: 0, + fees: { + feeDeposit: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefresh: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeRefund: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, + feeWithdraw: { + currency: "KUDOS", + fraction: 1000000, + value: 0, + }, }, isOffered: true, isRevoked: false, @@ -352,11 +354,9 @@ test("withdrawal selection bug repro", (t) => { t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, - value: { - currency: "KUDOS", - fraction: 0, - value: 2, - }, + amountFrac: 0, + amountVal: 2, + currency: "KUDOS", listIssueDate: { t_s: 0 }, }, ]; diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index bee83265c..47252a7e4 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -243,11 +243,19 @@ export function selectWithdrawalDenominations( let totalWithdrawCost = Amounts.getZero(amountAvailable.currency); denoms = denoms.filter(isWithdrawableDenom); - denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); + denoms.sort((d1, d2) => + Amounts.cmp( + DenominationRecord.getValue(d2), + DenominationRecord.getValue(d1), + ), + ); for (const d of denoms) { let count = 0; - const cost = Amounts.add(d.value, d.feeWithdraw).amount; + const cost = Amounts.add( + DenominationRecord.getValue(d), + d.fees.feeWithdraw, + ).amount; for (;;) { if (Amounts.cmp(remaining, cost) < 0) { break; @@ -258,7 +266,7 @@ export function selectWithdrawalDenominations( if (count > 0) { totalCoinValue = Amounts.add( totalCoinValue, - Amounts.mult(d.value, count).amount, + Amounts.mult(DenominationRecord.getValue(d), count).amount, ).amount; totalWithdrawCost = Amounts.add( totalWithdrawCost, @@ -306,22 +314,30 @@ export function selectForcedWithdrawalDenominations( let totalWithdrawCost = Amounts.getZero(amountAvailable.currency); denoms = denoms.filter(isWithdrawableDenom); - denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); + denoms.sort((d1, d2) => + Amounts.cmp( + DenominationRecord.getValue(d2), + DenominationRecord.getValue(d1), + ), + ); for (const fds of forcedDenomSel.denoms) { const count = fds.count; const denom = denoms.find((x) => { - return Amounts.cmp(x.value, fds.value) == 0; + return Amounts.cmp(DenominationRecord.getValue(x), fds.value) == 0; }); if (!denom) { throw Error( `unable to find denom for forced selection (value ${fds.value})`, ); } - const cost = Amounts.add(denom.value, denom.feeWithdraw).amount; + const cost = Amounts.add( + DenominationRecord.getValue(denom), + denom.fees.feeWithdraw, + ).amount; totalCoinValue = Amounts.add( totalCoinValue, - Amounts.mult(denom.value, count).amount, + Amounts.mult(DenominationRecord.getValue(denom), count).amount, ).amount; totalWithdrawCost = Amounts.add( totalWithdrawCost, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index afbee4e64..02ed8a61b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -102,6 +102,7 @@ import { CoinRecord, CoinSourceType, CoinStatus, + DenominationRecord, exportDb, importDb, OperationAttemptResult, @@ -731,14 +732,29 @@ async function getExchangeDetailedInfo( return; } - const denominations = await tx.denominations.indexes.byExchangeBaseUrl - .iter(ex.baseUrl) - .toArray(); + const denominationRecords = + await tx.denominations.indexes.byExchangeBaseUrl + .iter(ex.baseUrl) + .toArray(); - if (!denominations) { + if (!denominationRecords) { return; } + const denominations: DenomInfo[] = denominationRecords.map((x) => ({ + denomPub: x.denomPub, + denomPubHash: x.denomPubHash, + feeDeposit: x.fees.feeDeposit, + feeRefresh: x.fees.feeRefresh, + feeRefund: x.fees.feeRefund, + feeWithdraw: x.fees.feeWithdraw, + stampExpireDeposit: x.stampExpireDeposit, + stampExpireLegal: x.stampExpireLegal, + stampExpireWithdraw: x.stampExpireWithdraw, + stampStart: x.stampStart, + value: DenominationRecord.getValue(x), + })); + return { info: { exchangeBaseUrl: ex.baseUrl, @@ -753,7 +769,7 @@ async function getExchangeDetailedInfo( auditors: exchangeDetails.auditors, wireInfo: exchangeDetails.wireInfo, }, - denominations: denominations, + denominations, }; }); @@ -969,7 +985,11 @@ async function dumpCoins(ws: InternalWalletState): Promise { coin_pub: c.coinPub, denom_pub: denomInfo.denomPub, denom_pub_hash: c.denomPubHash, - denom_value: Amounts.stringify(denom.value), + denom_value: Amounts.stringify({ + value: denom.amountVal, + currency: denom.currency, + fraction: denom.amountFrac, + }), exchange_base_url: c.exchangeBaseUrl, refresh_parent_coin_pub: refreshParentCoinPub, remaining_value: Amounts.stringify(c.currentAmount), @@ -1566,9 +1586,22 @@ class InternalWalletStateImpl implements InternalWalletState { } const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]); if (d) { - this.denomCache[key] = d; + const denomInfo = { + denomPub: d.denomPub, + denomPubHash: d.denomPubHash, + feeDeposit: d.fees.feeDeposit, + feeRefresh: d.fees.feeRefresh, + feeRefund: d.fees.feeRefund, + feeWithdraw: d.fees.feeWithdraw, + stampExpireDeposit: d.stampExpireDeposit, + stampExpireLegal: d.stampExpireLegal, + stampExpireWithdraw: d.stampExpireWithdraw, + stampStart: d.stampStart, + value: DenominationRecord.getValue(d), + }; + return denomInfo; } - return d; + return undefined; } notify(n: WalletNotification): void { -- cgit v1.2.3