/* 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, Logger, TalerProtocolTimestamp, buildCodecForObject, codecForAmountString, codecForTimestamp, codecOptional, } from "@gnu-taler/taler-util"; import { SpendCoinDetails } from "../crypto/cryptoImplementation.js"; import { DenominationRecord, PeerPushPaymentCoinSelection, ReserveRecord, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import type { SelectedPeerCoin } from "../util/coinSelection.js"; import { checkDbInvariant } from "../util/invariants.js"; import { getTotalRefreshCost } from "./refresh.js"; const logger = new Logger("operations/peer-to-peer.ts"); /** * Get information about the coin selected for signatures * * @param ws * @param csel * @returns */ export async function queryCoinInfosForSelection( ws: InternalWalletState, csel: PeerPushPaymentCoinSelection, ): Promise { let infos: SpendCoinDetails[] = []; await ws.db .mktx((x) => [x.coins, x.denominations]) .runReadOnly(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 ws.getDenomInfo( ws, 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( ws: InternalWalletState, pcs: SelectedPeerCoin[], ): Promise { return ws.db .mktx((x) => [x.coins, x.denominations]) .runReadOnly(async (tx) => { const costs: AmountJson[] = []; for (let i = 0; i < pcs.length; i++) { const coin = await tx.coins.get(pcs[i].coinPub); if (!coin) { throw Error("can't calculate payment cost, coin not found"); } const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); if (!denom) { throw Error( "can't calculate payment cost, denomination for coin not found", ); } const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl .iter(coin.exchangeBaseUrl) .filter((x) => Amounts.isSameCurrency( DenominationRecord.getValue(x), pcs[i].contribution, ), ); const amountLeft = Amounts.sub( DenominationRecord.getValue(denom), pcs[i].contribution, ).amount; const refreshCost = getTotalRefreshCost( allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft, ws.config.testing.denomselAllowLate, ); 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( ws: InternalWalletState, 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 ws.cryptoApi.createEddsaKeypair({}); const mergeReserveRecord: ReserveRecord = await ws.db .mktx((x) => [x.exchanges, x.reserves, x.withdrawalGroups]) .runReadWrite(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; }