/*
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,
TalerProtocolTimestamp,
buildCodecForObject,
checkDbInvariant,
codecForAmountString,
codecForTimestamp,
codecOptional,
} from "@gnu-taler/taler-util";
import type { SelectedPeerCoin } from "./coinSelection.js";
import { SpendCoinDetails } from "./crypto/cryptoImplementation.js";
import { PeerPushPaymentCoinSelection, ReserveRecord } from "./db.js";
import { getTotalRefreshCost } from "./refresh.js";
import { InternalWalletState } from "./wallet.js";
import { getCandidateWithdrawalDenomsTx } from "./withdraw.js";
/**
* Get information about the coin selected for signatures.
*/
export async function queryCoinInfosForSelection(
ws: InternalWalletState,
csel: PeerPushPaymentCoinSelection,
): Promise {
let infos: SpendCoinDetails[] = [];
await ws.db.runReadOnlyTx(["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 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 {
const currency = Amounts.currencyOf(pcs[0].contribution);
return ws.db.runReadOnlyTx(["coins", "denominations"], 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 denomInfo = await ws.getDenomInfo(
ws,
tx,
coin.exchangeBaseUrl,
coin.denomPubHash,
);
if (!denomInfo) {
throw Error(
"can't calculate payment cost, denomination for coin not found",
);
}
const allDenoms = await getCandidateWithdrawalDenomsTx(
ws,
tx,
coin.exchangeBaseUrl,
currency,
);
const amountLeft = Amounts.sub(
denomInfo.value,
pcs[i].contribution,
).amount;
const refreshCost = getTotalRefreshCost(
allDenoms,
denomInfo,
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.runReadWriteTx(
["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;
}