wallet-core: allow failure result in peer payment coin selection
This commit is contained in:
parent
80639429a2
commit
c2c35925bb
@ -3240,6 +3240,14 @@ export enum TalerErrorCode {
|
||||
WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE = 7026,
|
||||
|
||||
|
||||
/**
|
||||
* The wallet does not have sufficient balance to create a peer push payment.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
|
||||
* (A value of 0 indicates that the error is generated client-side).
|
||||
*/
|
||||
WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE = 7027,
|
||||
|
||||
|
||||
/**
|
||||
* We encountered a timeout with our payment backend.
|
||||
* Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
|
||||
|
@ -419,7 +419,10 @@ export const codecForPreparePayResultInsufficientBalance =
|
||||
"status",
|
||||
codecForConstString(PreparePayResultType.InsufficientBalance),
|
||||
)
|
||||
.property("balanceDetails", codecForPayMerchantInsufficientBalanceDetails())
|
||||
.property(
|
||||
"balanceDetails",
|
||||
codecForPayMerchantInsufficientBalanceDetails(),
|
||||
)
|
||||
.build("PreparePayResultInsufficientBalance");
|
||||
|
||||
export const codecForPreparePayResultAlreadyConfirmed =
|
||||
@ -2084,7 +2087,6 @@ export interface InitiatePeerPullPaymentResponse {
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detailed reason for why the wallet's balance is insufficient.
|
||||
*/
|
||||
@ -2124,23 +2126,58 @@ export interface PayMerchantInsufficientBalanceDetails {
|
||||
* (i.e. balanceMechantWireable >= amountRequested),
|
||||
* this field contains an estimate of the amount that would additionally
|
||||
* be required to cover the fees.
|
||||
*
|
||||
*
|
||||
* It is not possible to give an exact value here, since it depends
|
||||
* on the coin selection for the amount that would be additionally withdrawn.
|
||||
*/
|
||||
feeGapEstimate: AmountString;
|
||||
}
|
||||
|
||||
const codecForPayMerchantInsufficientBalanceDetails =
|
||||
(): Codec<PayMerchantInsufficientBalanceDetails> =>
|
||||
buildCodecForObject<PayMerchantInsufficientBalanceDetails>()
|
||||
.property("amountRequested", codecForAmountString())
|
||||
.property("balanceAgeAcceptable", codecForAmountString())
|
||||
.property("balanceAvailable", codecForAmountString())
|
||||
.property("balanceMaterial", codecForAmountString())
|
||||
.property("balanceMerchantAcceptable", codecForAmountString())
|
||||
.property("balanceMerchantDepositable", codecForAmountString())
|
||||
.property("feeGapEstimate", codecForAmountString())
|
||||
.build("PayMerchantInsufficientBalanceDetails");
|
||||
export const codecForPayMerchantInsufficientBalanceDetails =
|
||||
(): Codec<PayMerchantInsufficientBalanceDetails> =>
|
||||
buildCodecForObject<PayMerchantInsufficientBalanceDetails>()
|
||||
.property("amountRequested", codecForAmountString())
|
||||
.property("balanceAgeAcceptable", codecForAmountString())
|
||||
.property("balanceAvailable", codecForAmountString())
|
||||
.property("balanceMaterial", codecForAmountString())
|
||||
.property("balanceMerchantAcceptable", codecForAmountString())
|
||||
.property("balanceMerchantDepositable", codecForAmountString())
|
||||
.property("feeGapEstimate", codecForAmountString())
|
||||
.build("PayMerchantInsufficientBalanceDetails");
|
||||
|
||||
/**
|
||||
* Detailed reason for why the wallet's balance is insufficient.
|
||||
*/
|
||||
export interface PayPeerInsufficientBalanceDetails {
|
||||
/**
|
||||
* Amount requested by the merchant.
|
||||
*/
|
||||
amountRequested: AmountString;
|
||||
|
||||
/**
|
||||
* Balance of type "available" (see balance.ts for definition).
|
||||
*/
|
||||
balanceAvailable: AmountString;
|
||||
|
||||
/**
|
||||
* Balance of type "material" (see balance.ts for definition).
|
||||
*/
|
||||
balanceMaterial: AmountString;
|
||||
|
||||
/**
|
||||
* Acceptable balance based on restrictions on which
|
||||
* exchange can be used.
|
||||
*/
|
||||
balanceExchangeAcceptable: AmountString
|
||||
|
||||
/**
|
||||
* If the payment would succeed without fees
|
||||
* (i.e. balanceExchangeAcceptable >= amountRequested),
|
||||
* this field contains an estimate of the amount that would additionally
|
||||
* be required to cover the fees.
|
||||
*
|
||||
* It is not possible to give an exact value here, since it depends
|
||||
* on the coin selection for the amount that would be additionally withdrawn.
|
||||
*/
|
||||
feeGapEstimate: AmountString;
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AcceptPeerPullPaymentRequest,
|
||||
AcceptPeerPullPaymentResponse,
|
||||
AcceptPeerPushPaymentRequest,
|
||||
@ -41,7 +40,6 @@ import {
|
||||
constructPayPushUri,
|
||||
ContractTermsUtil,
|
||||
decodeCrock,
|
||||
Duration,
|
||||
eddsaGetPublic,
|
||||
encodeCrock,
|
||||
ExchangePurseDeposits,
|
||||
@ -56,6 +54,7 @@ import {
|
||||
Logger,
|
||||
parsePayPullUri,
|
||||
parsePayPushUri,
|
||||
PayPeerInsufficientBalanceDetails,
|
||||
PeerContractTerms,
|
||||
PreparePeerPullPaymentRequest,
|
||||
PreparePeerPullPaymentResponse,
|
||||
@ -132,6 +131,12 @@ interface CoinInfo {
|
||||
ageCommitmentProof?: AgeCommitmentProof;
|
||||
}
|
||||
|
||||
export type SelectPeerCoinsResult =
|
||||
| { type: "success"; result: PeerCoinSelection }
|
||||
| {
|
||||
type: "failure";
|
||||
};
|
||||
|
||||
export async function selectPeerCoins(
|
||||
ws: InternalWalletState,
|
||||
tx: GetReadOnlyAccess<{
|
||||
@ -140,7 +145,7 @@ export async function selectPeerCoins(
|
||||
coins: typeof WalletStoresV1.coins;
|
||||
}>,
|
||||
instructedAmount: AmountJson,
|
||||
): Promise<PeerCoinSelection | undefined> {
|
||||
): Promise<SelectPeerCoinsResult> {
|
||||
const exchanges = await tx.exchanges.iter().toArray();
|
||||
for (const exch of exchanges) {
|
||||
if (exch.detailsPointer?.currency !== instructedAmount.currency) {
|
||||
@ -218,11 +223,11 @@ export async function selectPeerCoins(
|
||||
coins: resCoins,
|
||||
depositFees: depositFeesAcc,
|
||||
};
|
||||
return res;
|
||||
return { type: "success", result: res };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return undefined;
|
||||
return { type: "failure" };
|
||||
}
|
||||
|
||||
export async function preparePeerPushPayment(
|
||||
@ -258,7 +263,7 @@ export async function initiatePeerToPeerPush(
|
||||
pursePub: pursePair.pub,
|
||||
});
|
||||
|
||||
const coinSelRes: PeerCoinSelection | undefined = await ws.db
|
||||
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
||||
.mktx((x) => [
|
||||
x.exchanges,
|
||||
x.contractTerms,
|
||||
@ -270,11 +275,13 @@ export async function initiatePeerToPeerPush(
|
||||
x.peerPushPaymentInitiations,
|
||||
])
|
||||
.runReadWrite(async (tx) => {
|
||||
const sel = await selectPeerCoins(ws, tx, instructedAmount);
|
||||
if (!sel) {
|
||||
return undefined;
|
||||
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
||||
if (selRes.type === "failure") {
|
||||
return selRes;
|
||||
}
|
||||
|
||||
const sel = selRes.result;
|
||||
|
||||
await spendCoins(ws, tx, {
|
||||
allocationId: `txn:peer-push-debit:${pursePair.pub}`,
|
||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||
@ -304,11 +311,12 @@ export async function initiatePeerToPeerPush(
|
||||
contractTermsRaw: contractTerms,
|
||||
});
|
||||
|
||||
return sel;
|
||||
return selRes;
|
||||
});
|
||||
logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
|
||||
|
||||
if (!coinSelRes) {
|
||||
if (coinSelRes.type !== "success") {
|
||||
// FIXME: use error code with details here
|
||||
throw Error("insufficient balance");
|
||||
}
|
||||
|
||||
@ -322,14 +330,14 @@ export async function initiatePeerToPeerPush(
|
||||
});
|
||||
|
||||
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
||||
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
|
||||
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
||||
pursePub: pursePair.pub,
|
||||
coins: coinSelRes.coins,
|
||||
coins: coinSelRes.result.coins,
|
||||
});
|
||||
|
||||
const createPurseUrl = new URL(
|
||||
`purses/${pursePair.pub}/create`,
|
||||
coinSelRes.exchangeBaseUrl,
|
||||
coinSelRes.result.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const httpResp = await ws.http.postJson(createPurseUrl.href, {
|
||||
@ -355,9 +363,9 @@ export async function initiatePeerToPeerPush(
|
||||
contractPriv: econtractResp.contractPriv,
|
||||
mergePriv: mergePair.priv,
|
||||
pursePub: pursePair.pub,
|
||||
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
|
||||
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
||||
talerUri: constructPayPushUri({
|
||||
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
|
||||
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
|
||||
contractPriv: econtractResp.contractPriv,
|
||||
}),
|
||||
transactionId: makeTransactionId(
|
||||
@ -627,7 +635,7 @@ export async function acceptPeerPullPayment(
|
||||
const instructedAmount = Amounts.parseOrThrow(
|
||||
peerPullInc.contractTerms.amount,
|
||||
);
|
||||
const coinSelRes: PeerCoinSelection | undefined = await ws.db
|
||||
const coinSelRes: SelectPeerCoinsResult = await ws.db
|
||||
.mktx((x) => [
|
||||
x.exchanges,
|
||||
x.coins,
|
||||
@ -637,11 +645,13 @@ export async function acceptPeerPullPayment(
|
||||
x.coinAvailability,
|
||||
])
|
||||
.runReadWrite(async (tx) => {
|
||||
const sel = await selectPeerCoins(ws, tx, instructedAmount);
|
||||
if (!sel) {
|
||||
return undefined;
|
||||
const selRes = await selectPeerCoins(ws, tx, instructedAmount);
|
||||
if (selRes.type !== "success") {
|
||||
return selRes;
|
||||
}
|
||||
|
||||
const sel = selRes.result;
|
||||
|
||||
await spendCoins(ws, tx, {
|
||||
allocationId: `txn:peer-pull-debit:${req.peerPullPaymentIncomingId}`,
|
||||
coinPubs: sel.coins.map((x) => x.coinPub),
|
||||
@ -660,25 +670,27 @@ export async function acceptPeerPullPayment(
|
||||
pi.status = PeerPullPaymentIncomingStatus.Accepted;
|
||||
await tx.peerPullPaymentIncoming.put(pi);
|
||||
|
||||
return sel;
|
||||
return selRes;
|
||||
});
|
||||
logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
|
||||
|
||||
if (!coinSelRes) {
|
||||
if (coinSelRes.type !== "success") {
|
||||
throw Error("insufficient balance");
|
||||
}
|
||||
|
||||
const pursePub = peerPullInc.pursePub;
|
||||
|
||||
const coinSel = coinSelRes.result;
|
||||
|
||||
const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
|
||||
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
|
||||
exchangeBaseUrl: coinSel.exchangeBaseUrl,
|
||||
pursePub,
|
||||
coins: coinSelRes.coins,
|
||||
coins: coinSel.coins,
|
||||
});
|
||||
|
||||
const purseDepositUrl = new URL(
|
||||
`purses/${pursePub}/deposit`,
|
||||
coinSelRes.exchangeBaseUrl,
|
||||
coinSel.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const depositPayload: ExchangePurseDeposits = {
|
||||
@ -770,6 +782,7 @@ export async function preparePeerPullPayment(
|
||||
amountRaw: req.amount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate a peer pull payment.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user