/*
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, `no exchange record for ${req.exchangeBaseUrl}`);
if (ex.currentMergeReserveRowId != null) {
const reserve = await tx.reserves.get(ex.currentMergeReserveRowId);
checkDbInvariant(!!reserve, `reserver ${ex.currentMergeReserveRowId} missing in db`);
return reserve;
}
const reserve: ReserveRecord = {
reservePriv: newReservePair.priv,
reservePub: newReservePair.pub,
};
const insertResp = await tx.reserves.put(reserve);
checkDbInvariant(typeof insertResp.key === "number", `reserve key is not a number`);
reserve.rowId = insertResp.key;
ex.currentMergeReserveRowId = reserve.rowId;
await tx.exchanges.put(ex);
return reserve;
},
);
return mergeReserveRecord;
}