wallet-core: store total p2p push cost in DB

This commit is contained in:
Florian Dold 2023-01-13 02:24:19 +01:00
parent a3f9e86805
commit a31b8c3c31
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
3 changed files with 197 additions and 198 deletions

View File

@ -1713,6 +1713,8 @@ export interface PeerPushPaymentInitiationRecord {
*/ */
amount: AmountString; amount: AmountString;
totalCost: AmountString;
coinSel: PeerPushPaymentCoinSelection; coinSel: PeerPushPaymentCoinSelection;
contractTermsHash: HashCodeString; contractTermsHash: HashCodeString;

View File

@ -103,20 +103,22 @@ import { internalCreateWithdrawalGroup } from "./withdraw.js";
const logger = new Logger("operations/peer-to-peer.ts"); const logger = new Logger("operations/peer-to-peer.ts");
export interface PeerCoinSelectionDetails { interface SelectedPeerCoin {
exchangeBaseUrl: string;
/**
* Info of Coins that were selected.
*/
coins: {
coinPub: string; coinPub: string;
coinPriv: string; coinPriv: string;
contribution: AmountString; contribution: AmountString;
denomPubHash: string; denomPubHash: string;
denomSig: UnblindedSignature; denomSig: UnblindedSignature;
ageCommitmentProof: AgeCommitmentProof | undefined; ageCommitmentProof: AgeCommitmentProof | undefined;
}[]; }
interface PeerCoinSelectionDetails {
exchangeBaseUrl: string;
/**
* Info of Coins that were selected.
*/
coins: SelectedPeerCoin[];
/** /**
* How much of the deposit fees is the customer paying? * How much of the deposit fees is the customer paying?
@ -195,15 +197,19 @@ export async function queryCoinInfosForSelection(
export async function selectPeerCoins( export async function selectPeerCoins(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadOnlyAccess<{
exchanges: typeof WalletStoresV1.exchanges;
denominations: typeof WalletStoresV1.denominations;
coins: typeof WalletStoresV1.coins;
coinAvailability: typeof WalletStoresV1.coinAvailability;
refreshGroups: typeof WalletStoresV1.refreshGroups;
}>,
instructedAmount: AmountJson, instructedAmount: AmountJson,
): Promise<SelectPeerCoinsResult> { ): Promise<SelectPeerCoinsResult> {
return await ws.db
.mktx((x) => [
x.exchanges,
x.contractTerms,
x.coins,
x.coinAvailability,
x.denominations,
x.refreshGroups,
x.peerPushPaymentInitiations,
])
.runReadWrite(async (tx) => {
const exchanges = await tx.exchanges.iter().toArray(); const exchanges = await tx.exchanges.iter().toArray();
const exchangeFeeGap: { [url: string]: AmountJson } = {}; const exchangeFeeGap: { [url: string]: AmountJson } = {};
const currency = Amounts.currencyOf(instructedAmount); const currency = Amounts.currencyOf(instructedAmount);
@ -309,7 +315,8 @@ export async function selectPeerCoins(
currency, currency,
restrictExchangeTo: exch.baseUrl, restrictExchangeTo: exch.baseUrl,
}); });
let gap = exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency); let gap =
exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) { if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
// Show fee gap only if we should've been able to pay with the material amount // Show fee gap only if we should've been able to pay with the material amount
gap = Amounts.zeroOfAmount(currency); gap = Amounts.zeroOfAmount(currency);
@ -329,18 +336,19 @@ export async function selectPeerCoins(
}; };
return { type: "failure", insufficientBalanceDetails: errDetails }; return { type: "failure", insufficientBalanceDetails: errDetails };
});
} }
export async function getTotalPeerPaymentCost( export async function getTotalPeerPaymentCost(
ws: InternalWalletState, ws: InternalWalletState,
pcs: PeerCoinSelectionDetails, pcs: SelectedPeerCoin[],
): Promise<AmountJson> { ): Promise<AmountJson> {
return ws.db return ws.db
.mktx((x) => [x.coins, x.denominations]) .mktx((x) => [x.coins, x.denominations])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const costs: AmountJson[] = []; const costs: AmountJson[] = [];
for (let i = 0; i < pcs.coins.length; i++) { for (let i = 0; i < pcs.length; i++) {
const coin = await tx.coins.get(pcs.coins[i].coinPub); const coin = await tx.coins.get(pcs[i].coinPub);
if (!coin) { if (!coin) {
throw Error("can't calculate payment cost, coin not found"); throw Error("can't calculate payment cost, coin not found");
} }
@ -358,22 +366,22 @@ export async function getTotalPeerPaymentCost(
.filter((x) => .filter((x) =>
Amounts.isSameCurrency( Amounts.isSameCurrency(
DenominationRecord.getValue(x), DenominationRecord.getValue(x),
pcs.coins[i].contribution, pcs[i].contribution,
), ),
); );
const amountLeft = Amounts.sub( const amountLeft = Amounts.sub(
DenominationRecord.getValue(denom), DenominationRecord.getValue(denom),
pcs.coins[i].contribution, pcs[i].contribution,
).amount; ).amount;
const refreshCost = getTotalRefreshCost( const refreshCost = getTotalRefreshCost(
allDenoms, allDenoms,
DenominationRecord.toDenomInfo(denom), DenominationRecord.toDenomInfo(denom),
amountLeft, amountLeft,
); );
costs.push(Amounts.parseOrThrow(pcs.coins[i].contribution)); costs.push(Amounts.parseOrThrow(pcs[i].contribution));
costs.push(refreshCost); costs.push(refreshCost);
} }
const zero = Amounts.zeroOfAmount(pcs.coins[0].contribution); const zero = Amounts.zeroOfAmount(pcs[0].contribution);
return Amounts.sum([zero, ...costs]).amount; return Amounts.sum([zero, ...costs]).amount;
}); });
} }
@ -383,20 +391,7 @@ export async function preparePeerPushPayment(
req: PreparePeerPushPaymentRequest, req: PreparePeerPushPaymentRequest,
): Promise<PreparePeerPushPaymentResponse> { ): Promise<PreparePeerPushPaymentResponse> {
const instructedAmount = Amounts.parseOrThrow(req.amount); const instructedAmount = Amounts.parseOrThrow(req.amount);
const coinSelRes: SelectPeerCoinsResult = await ws.db const coinSelRes = await selectPeerCoins(ws, instructedAmount);
.mktx((x) => [
x.exchanges,
x.contractTerms,
x.coins,
x.coinAvailability,
x.denominations,
x.refreshGroups,
x.peerPushPaymentInitiations,
])
.runReadWrite(async (tx) => {
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
return selRes;
});
if (coinSelRes.type === "failure") { if (coinSelRes.type === "failure") {
throw TalerError.fromDetail( throw TalerError.fromDetail(
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE, TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
@ -405,7 +400,10 @@ export async function preparePeerPushPayment(
}, },
); );
} }
const totalAmount = await getTotalPeerPaymentCost(ws, coinSelRes.result); const totalAmount = await getTotalPeerPaymentCost(
ws,
coinSelRes.result.coins,
);
return { return {
amountEffective: Amounts.stringify(totalAmount), amountEffective: Amounts.stringify(totalAmount),
amountRaw: req.amount, amountRaw: req.amount,
@ -517,7 +515,28 @@ export async function initiatePeerPushPayment(
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({}); const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({});
const coinSelRes: SelectPeerCoinsResult = await ws.db
const coinSelRes = await selectPeerCoins(ws, instructedAmount);
if (coinSelRes.type !== "success") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
{
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
},
);
}
const sel = coinSelRes.result;
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
const totalAmount = await getTotalPeerPaymentCost(
ws,
coinSelRes.result.coins,
);
await ws.db
.mktx((x) => [ .mktx((x) => [
x.exchanges, x.exchanges,
x.contractTerms, x.contractTerms,
@ -528,13 +547,6 @@ export async function initiatePeerPushPayment(
x.peerPushPaymentInitiations, x.peerPushPaymentInitiations,
]) ])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
if (selRes.type === "failure") {
return selRes;
}
const sel = selRes.result;
await spendCoins(ws, tx, { await spendCoins(ws, tx, {
allocationId: `txn:peer-push-debit:${pursePair.pub}`, allocationId: `txn:peer-push-debit:${pursePair.pub}`,
coinPubs: sel.coins.map((x) => x.coinPub), coinPubs: sel.coins.map((x) => x.coinPub),
@ -562,25 +574,14 @@ export async function initiatePeerPushPayment(
coinPubs: sel.coins.map((x) => x.coinPub), coinPubs: sel.coins.map((x) => x.coinPub),
contributions: sel.coins.map((x) => x.contribution), contributions: sel.coins.map((x) => x.contribution),
}, },
totalCost: Amounts.stringify(totalAmount),
}); });
await tx.contractTerms.put({ await tx.contractTerms.put({
h: hContractTerms, h: hContractTerms,
contractTermsRaw: contractTerms, contractTermsRaw: contractTerms,
}); });
return selRes;
}); });
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
if (coinSelRes.type !== "success") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
{
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
},
);
}
await runOperationWithErrorReporting( await runOperationWithErrorReporting(
ws, ws,
@ -866,7 +867,22 @@ export async function acceptPeerPullPayment(
const instructedAmount = Amounts.parseOrThrow( const instructedAmount = Amounts.parseOrThrow(
peerPullInc.contractTerms.amount, peerPullInc.contractTerms.amount,
); );
const coinSelRes: SelectPeerCoinsResult = await ws.db
const coinSelRes = await selectPeerCoins(ws, instructedAmount);
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
if (coinSelRes.type !== "success") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
{
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
},
);
}
const sel = coinSelRes.result;
await ws.db
.mktx((x) => [ .mktx((x) => [
x.exchanges, x.exchanges,
x.coins, x.coins,
@ -876,13 +892,6 @@ export async function acceptPeerPullPayment(
x.coinAvailability, x.coinAvailability,
]) ])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
if (selRes.type !== "success") {
return selRes;
}
const sel = selRes.result;
await spendCoins(ws, tx, { await spendCoins(ws, tx, {
allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`, allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`,
coinPubs: sel.coins.map((x) => x.coinPub), coinPubs: sel.coins.map((x) => x.coinPub),
@ -900,19 +909,7 @@ export async function acceptPeerPullPayment(
} }
pi.status = PeerPullPaymentIncomingStatus.Accepted; pi.status = PeerPullPaymentIncomingStatus.Accepted;
await tx.peerPullPaymentIncoming.put(pi); await tx.peerPullPaymentIncoming.put(pi);
return selRes;
}); });
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
if (coinSelRes.type !== "success") {
throw TalerError.fromDetail(
TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
{
insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
},
);
}
const pursePub = peerPullInc.pursePub; const pursePub = peerPullInc.pursePub;

View File

@ -346,7 +346,7 @@ function buildTransactionForPushPaymentDebit(
): Transaction { ): Transaction {
return { return {
type: TransactionType.PeerPushDebit, type: TransactionType.PeerPushDebit,
amountEffective: pi.amount, amountEffective: pi.totalCost,
amountRaw: pi.amount, amountRaw: pi.amount,
exchangeBaseUrl: pi.exchangeBaseUrl, exchangeBaseUrl: pi.exchangeBaseUrl,
info: { info: {