/* This file is part of GNU Taler (C) 2022 GNUnet e.V. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * Imports. */ import { AmountJson, AmountString, Amounts, Codec, SelectedProspectiveCoin, TalerProtocolTimestamp, buildCodecForObject, checkDbInvariant, codecForAmountString, codecForTimestamp, codecOptional, } from "@gnu-taler/taler-util"; import { SpendCoinDetails } from "./crypto/cryptoImplementation.js"; import { DbPeerPushPaymentCoinSelection, ReserveRecord } from "./db.js"; import { getTotalRefreshCost } from "./refresh.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; /** * Get information about the coin selected for signatures. */ export async function queryCoinInfosForSelection( wex: WalletExecutionContext, csel: DbPeerPushPaymentCoinSelection, ): Promise { let infos: SpendCoinDetails[] = []; await wex.db.runReadOnlyTx( { storeNames: ["coins", "denominations"] }, async (tx) => { for (let i = 0; i < csel.coinPubs.length; i++) { const coin = await tx.coins.get(csel.coinPubs[i]); if (!coin) { throw Error("coin not found anymore"); } const denom = await getDenomInfo( wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, ); if (!denom) { throw Error("denom for coin not found anymore"); } infos.push({ coinPriv: coin.coinPriv, coinPub: coin.coinPub, denomPubHash: coin.denomPubHash, denomSig: coin.denomSig, ageCommitmentProof: coin.ageCommitmentProof, contribution: csel.contributions[i], }); } }, ); return infos; } export async function getTotalPeerPaymentCost( wex: WalletExecutionContext, pcs: SelectedProspectiveCoin[], ): Promise { return wex.db.runReadOnlyTx( { storeNames: ["coins", "denominations"] }, async (tx) => { const costs: AmountJson[] = []; for (let i = 0; i < pcs.length; i++) { const denomInfo = await getDenomInfo( wex, tx, pcs[i].exchangeBaseUrl, pcs[i].denomPubHash, ); if (!denomInfo) { throw Error( "can't calculate payment cost, denomination for coin not found", ); } const amountLeft = Amounts.sub( denomInfo.value, pcs[i].contribution, ).amount; const refreshCost = await getTotalRefreshCost( wex, tx, denomInfo, amountLeft, ); costs.push(Amounts.parseOrThrow(pcs[i].contribution)); costs.push(refreshCost); } const zero = Amounts.zeroOfAmount(pcs[0].contribution); return Amounts.sum([zero, ...costs]).amount; }, ); } interface ExchangePurseStatus { balance: AmountString; deposit_timestamp?: TalerProtocolTimestamp; merge_timestamp?: TalerProtocolTimestamp; } export const codecForExchangePurseStatus = (): Codec => buildCodecForObject() .property("balance", codecForAmountString()) .property("deposit_timestamp", codecOptional(codecForTimestamp)) .property("merge_timestamp", codecOptional(codecForTimestamp)) .build("ExchangePurseStatus"); export async function getMergeReserveInfo( wex: WalletExecutionContext, req: { exchangeBaseUrl: string; }, ): Promise { // We have to eagerly create the key pair outside of the transaction, // due to the async crypto API. const newReservePair = await wex.cryptoApi.createEddsaKeypair({}); const mergeReserveRecord: ReserveRecord = await wex.db.runReadWriteTx( { storeNames: ["exchanges", "reserves"] }, async (tx) => { const ex = await tx.exchanges.get(req.exchangeBaseUrl); checkDbInvariant(!!ex); if (ex.currentMergeReserveRowId != null) { const reserve = await tx.reserves.get(ex.currentMergeReserveRowId); checkDbInvariant(!!reserve); return reserve; } const reserve: ReserveRecord = { reservePriv: newReservePair.priv, reservePub: newReservePair.pub, }; const insertResp = await tx.reserves.put(reserve); checkDbInvariant(typeof insertResp.key === "number"); reserve.rowId = insertResp.key; ex.currentMergeReserveRowId = reserve.rowId; await tx.exchanges.put(ex); return reserve; }, ); return mergeReserveRecord; }