wallet: timeout handling refactoring WIP
This commit is contained in:
parent
c194bd539a
commit
80e43db2ca
@ -129,6 +129,17 @@ export class Amounts {
|
||||
return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1));
|
||||
}
|
||||
|
||||
static sumOrZero(currency: string, amounts: AmountLike[]): Result {
|
||||
if (amounts.length <= 0) {
|
||||
return {
|
||||
amount: Amounts.getZero(currency),
|
||||
saturated: false,
|
||||
};
|
||||
}
|
||||
const jsonAmounts = amounts.map((x) => Amounts.jsonifyAmount(x));
|
||||
return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add two amounts. Return the result and whether
|
||||
* the addition overflowed. The overflow is always handled
|
||||
|
@ -14,6 +14,9 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { TalerErrorDetail, TalerErrorCode } from "@gnu-taler/taler-util";
|
||||
import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js";
|
||||
import { TalerError, getErrorDetailFromException } from "../errors.js";
|
||||
|
@ -14,17 +14,15 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
buildCodecForObject,
|
||||
canonicalJson,
|
||||
Codec,
|
||||
codecForDepositSuccess,
|
||||
codecForString,
|
||||
codecForTimestamp,
|
||||
codecOptional,
|
||||
ContractTerms,
|
||||
CreateDepositGroupRequest,
|
||||
CreateDepositGroupResponse,
|
||||
@ -42,21 +40,22 @@ import {
|
||||
TrackDepositGroupResponse,
|
||||
URL,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import { DepositGroupRecord, OperationStatus } from "../db.js";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import { PayCoinSelection, selectPayCoins } from "../util/coinSelection.js";
|
||||
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
|
||||
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
|
||||
import { initRetryInfo, RetryInfo } from "../util/retries.js";
|
||||
import { guardOperationException } from "./common.js";
|
||||
import { getExchangeDetails } from "./exchanges.js";
|
||||
import {
|
||||
applyCoinSpend,
|
||||
CoinSelectionRequest,
|
||||
extractContractData,
|
||||
generateDepositPermissions,
|
||||
getCandidatePayCoins,
|
||||
getTotalPaymentCost,
|
||||
} from "./pay.js";
|
||||
import { getTotalRefreshCost } from "./refresh.js";
|
||||
import { guardOperationException } from "./common.js";
|
||||
|
||||
/**
|
||||
* Logger.
|
||||
@ -73,17 +72,36 @@ async function resetDepositGroupRetry(
|
||||
}))
|
||||
.runReadWrite(async (tx) => {
|
||||
const x = await tx.depositGroups.get(depositGroupId);
|
||||
if (x) {
|
||||
x.retryInfo = initRetryInfo();
|
||||
await tx.depositGroups.put(x);
|
||||
if (!x) {
|
||||
return;
|
||||
}
|
||||
x.retryInfo = initRetryInfo();
|
||||
delete x.lastError;
|
||||
await tx.depositGroups.put(x);
|
||||
});
|
||||
}
|
||||
|
||||
async function incrementDepositRetry(
|
||||
async function incrementDepositGroupRetry(
|
||||
ws: InternalWalletState,
|
||||
depositGroupId: string,
|
||||
err: TalerErrorDetail | undefined,
|
||||
): Promise<void> {
|
||||
await ws.db
|
||||
.mktx((x) => ({ depositGroups: x.depositGroups }))
|
||||
.runReadWrite(async (tx) => {
|
||||
const r = await tx.depositGroups.get(depositGroupId);
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
r.retryInfo = RetryInfo.increment(r.retryInfo);
|
||||
delete r.lastError;
|
||||
await tx.depositGroups.put(r);
|
||||
});
|
||||
}
|
||||
|
||||
async function reportDepositGroupError(
|
||||
ws: InternalWalletState,
|
||||
depositGroupId: string,
|
||||
err: TalerErrorDetail,
|
||||
): Promise<void> {
|
||||
await ws.db
|
||||
.mktx((x) => ({ depositGroups: x.depositGroups }))
|
||||
@ -93,16 +111,15 @@ async function incrementDepositRetry(
|
||||
return;
|
||||
}
|
||||
if (!r.retryInfo) {
|
||||
logger.error(
|
||||
`deposit group record (${depositGroupId}) reports error, but no retry active`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
r.retryInfo.retryCounter++;
|
||||
updateRetryInfoTimeout(r.retryInfo);
|
||||
r.lastError = err;
|
||||
await tx.depositGroups.put(r);
|
||||
});
|
||||
if (err) {
|
||||
ws.notify({ type: NotificationType.DepositOperationError, error: err });
|
||||
}
|
||||
ws.notify({ type: NotificationType.DepositOperationError, error: err });
|
||||
}
|
||||
|
||||
export async function processDepositGroup(
|
||||
@ -111,8 +128,8 @@ export async function processDepositGroup(
|
||||
forceNow = false,
|
||||
): Promise<void> {
|
||||
await ws.memoProcessDeposit.memo(depositGroupId, async () => {
|
||||
const onOpErr = (e: TalerErrorDetail): Promise<void> =>
|
||||
incrementDepositRetry(ws, depositGroupId, e);
|
||||
const onOpErr = (err: TalerErrorDetail): Promise<void> =>
|
||||
reportDepositGroupError(ws, depositGroupId, err);
|
||||
return await guardOperationException(
|
||||
async () => await processDepositGroupImpl(ws, depositGroupId, forceNow),
|
||||
onOpErr,
|
||||
@ -125,9 +142,6 @@ async function processDepositGroupImpl(
|
||||
depositGroupId: string,
|
||||
forceNow = false,
|
||||
): Promise<void> {
|
||||
if (forceNow) {
|
||||
await resetDepositGroupRetry(ws, depositGroupId);
|
||||
}
|
||||
const depositGroup = await ws.db
|
||||
.mktx((x) => ({
|
||||
depositGroups: x.depositGroups,
|
||||
@ -144,6 +158,12 @@ async function processDepositGroupImpl(
|
||||
return;
|
||||
}
|
||||
|
||||
if (forceNow) {
|
||||
await resetDepositGroupRetry(ws, depositGroupId);
|
||||
} else {
|
||||
await incrementDepositGroupRetry(ws, depositGroupId);
|
||||
}
|
||||
|
||||
const contractData = extractContractData(
|
||||
depositGroup.contractTermsRaw,
|
||||
depositGroup.contractTermsHash,
|
||||
@ -306,42 +326,25 @@ export async function getFeeForDeposit(
|
||||
}
|
||||
});
|
||||
|
||||
const timestamp = AbsoluteTime.now();
|
||||
const timestampRound = AbsoluteTime.toTimestamp(timestamp);
|
||||
const contractTerms: ContractTerms = {
|
||||
auditors: [],
|
||||
exchanges: exchangeInfos,
|
||||
amount: req.amount,
|
||||
max_fee: Amounts.stringify(amount),
|
||||
max_wire_fee: Amounts.stringify(amount),
|
||||
wire_method: p.targetType,
|
||||
timestamp: timestampRound,
|
||||
merchant_base_url: "",
|
||||
summary: "",
|
||||
nonce: "",
|
||||
wire_transfer_deadline: timestampRound,
|
||||
order_id: "",
|
||||
h_wire: "",
|
||||
pay_deadline: AbsoluteTime.toTimestamp(
|
||||
AbsoluteTime.addDuration(timestamp, durationFromSpec({ hours: 1 })),
|
||||
),
|
||||
merchant: {
|
||||
name: "",
|
||||
},
|
||||
merchant_pub: "",
|
||||
refund_deadline: TalerProtocolTimestamp.zero(),
|
||||
const csr: CoinSelectionRequest = {
|
||||
allowedAuditors: [],
|
||||
allowedExchanges: [],
|
||||
amount: Amounts.parseOrThrow(req.amount),
|
||||
maxDepositFee: Amounts.parseOrThrow(req.amount),
|
||||
maxWireFee: Amounts.parseOrThrow(req.amount),
|
||||
timestamp: TalerProtocolTimestamp.now(),
|
||||
wireFeeAmortization: 1,
|
||||
wireMethod: p.targetType,
|
||||
};
|
||||
|
||||
const contractData = extractContractData(contractTerms, "", "");
|
||||
|
||||
const candidates = await getCandidatePayCoins(ws, contractData);
|
||||
const candidates = await getCandidatePayCoins(ws, csr);
|
||||
|
||||
const payCoinSel = selectPayCoins({
|
||||
candidates,
|
||||
contractTermsAmount: contractData.amount,
|
||||
depositFeeLimit: contractData.maxDepositFee,
|
||||
wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
|
||||
wireFeeLimit: contractData.maxWireFee,
|
||||
contractTermsAmount: csr.amount,
|
||||
depositFeeLimit: csr.maxDepositFee,
|
||||
wireFeeAmortization: csr.wireFeeAmortization,
|
||||
wireFeeLimit: csr.maxWireFee,
|
||||
prevPayCoins: [],
|
||||
});
|
||||
|
||||
@ -573,6 +576,7 @@ export async function getEffectiveDepositAmount(
|
||||
return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount;
|
||||
}
|
||||
|
||||
// FIXME: rename to DepositGroupFee
|
||||
export interface DepositFee {
|
||||
coin: AmountJson;
|
||||
wire: AmountJson;
|
||||
@ -594,8 +598,6 @@ export async function getTotalFeeForDepositAmount(
|
||||
const refreshFee: AmountJson[] = [];
|
||||
const exchangeSet: Set<string> = new Set();
|
||||
|
||||
// let acc: AmountJson = Amounts.getZero(total.currency);
|
||||
|
||||
await ws.db
|
||||
.mktx((x) => ({
|
||||
coins: x.coins,
|
||||
@ -658,17 +660,8 @@ export async function getTotalFeeForDepositAmount(
|
||||
});
|
||||
|
||||
return {
|
||||
coin:
|
||||
coinFee.length === 0
|
||||
? Amounts.getZero(total.currency)
|
||||
: Amounts.sum(coinFee).amount,
|
||||
wire:
|
||||
wireFee.length === 0
|
||||
? Amounts.getZero(total.currency)
|
||||
: Amounts.sum(wireFee).amount,
|
||||
refresh:
|
||||
refreshFee.length === 0
|
||||
? Amounts.getZero(total.currency)
|
||||
: Amounts.sum(refreshFee).amount,
|
||||
coin: Amounts.sumOrZero(total.currency, coinFee).amount,
|
||||
wire: Amounts.sumOrZero(total.currency, wireFee).amount,
|
||||
refresh: Amounts.sumOrZero(total.currency, refreshFee).amount,
|
||||
};
|
||||
}
|
||||
|
@ -18,35 +18,31 @@
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
AbsoluteTime,
|
||||
Amounts,
|
||||
ExchangeAuditor,
|
||||
canonicalizeBaseUrl,
|
||||
codecForExchangeKeysJson,
|
||||
codecForExchangeWireJson,
|
||||
ExchangeDenomination,
|
||||
DenominationPubKey,
|
||||
Duration,
|
||||
durationFromSpec,
|
||||
encodeCrock,
|
||||
ExchangeAuditor,
|
||||
ExchangeDenomination,
|
||||
ExchangeSignKeyJson,
|
||||
ExchangeWireJson,
|
||||
hashDenomPub,
|
||||
LibtoolVersion,
|
||||
Logger,
|
||||
NotificationType,
|
||||
parsePaytoUri,
|
||||
Recoup,
|
||||
TalerErrorCode,
|
||||
URL,
|
||||
TalerErrorDetail,
|
||||
AbsoluteTime,
|
||||
hashDenomPub,
|
||||
LibtoolVersion,
|
||||
codecForAny,
|
||||
DenominationPubKey,
|
||||
DenomKeyType,
|
||||
ExchangeKeysJson,
|
||||
TalerProtocolTimestamp,
|
||||
TalerProtocolDuration,
|
||||
TalerProtocolTimestamp,
|
||||
URL,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
|
||||
import { CryptoDispatcher } from "../crypto/workers/cryptoDispatcher.js";
|
||||
import {
|
||||
DenominationRecord,
|
||||
DenominationVerificationStatus,
|
||||
@ -56,6 +52,8 @@ import {
|
||||
WireFee,
|
||||
WireInfo,
|
||||
} from "../db.js";
|
||||
import { TalerError } from "../errors.js";
|
||||
import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
|
||||
import {
|
||||
getExpiry,
|
||||
HttpRequestLibrary,
|
||||
@ -64,8 +62,6 @@ import {
|
||||
} from "../util/http.js";
|
||||
import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
|
||||
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
|
||||
import { TalerError } from "../errors.js";
|
||||
import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
|
||||
import {
|
||||
WALLET_CACHE_BREAKER_CLIENT_VERSION,
|
||||
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
||||
|
@ -98,6 +98,7 @@ import { GetReadWriteAccess } from "../util/query.js";
|
||||
import {
|
||||
getRetryDuration,
|
||||
initRetryInfo,
|
||||
RetryInfo,
|
||||
updateRetryInfoTimeout,
|
||||
} from "../util/retries.js";
|
||||
import { getExchangeDetails } from "./exchanges.js";
|
||||
@ -539,11 +540,7 @@ async function incrementPurchasePayRetry(
|
||||
if (!pr) {
|
||||
return;
|
||||
}
|
||||
if (!pr.payRetryInfo) {
|
||||
pr.payRetryInfo = initRetryInfo();
|
||||
}
|
||||
pr.payRetryInfo.retryCounter++;
|
||||
updateRetryInfoTimeout(pr.payRetryInfo);
|
||||
pr.payRetryInfo = RetryInfo.increment(pr.payRetryInfo);
|
||||
delete pr.lastPayError;
|
||||
await tx.purchases.put(pr);
|
||||
});
|
||||
|
@ -18,32 +18,31 @@
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
AbsoluteTime,
|
||||
AmountJson,
|
||||
Amounts,
|
||||
AmountString,
|
||||
BankWithdrawDetails,
|
||||
codecForTalerConfigResponse,
|
||||
codecForWithdrawOperationStatusResponse,
|
||||
codecForWithdrawResponse,
|
||||
DenomKeyType,
|
||||
Duration,
|
||||
durationFromSpec,
|
||||
ExchangeListItem,
|
||||
ExchangeWithdrawRequest,
|
||||
LibtoolVersion,
|
||||
Logger,
|
||||
NotificationType,
|
||||
parseWithdrawUri,
|
||||
TalerErrorCode,
|
||||
TalerErrorDetail,
|
||||
AbsoluteTime,
|
||||
WithdrawResponse,
|
||||
URL,
|
||||
WithdrawUriInfoResponse,
|
||||
VersionMatchResult,
|
||||
DenomKeyType,
|
||||
LibtoolVersion,
|
||||
UnblindedSignature,
|
||||
ExchangeWithdrawRequest,
|
||||
Duration,
|
||||
TalerProtocolTimestamp,
|
||||
TransactionType,
|
||||
AmountString,
|
||||
UnblindedSignature,
|
||||
URL,
|
||||
VersionMatchResult,
|
||||
WithdrawResponse,
|
||||
WithdrawUriInfoResponse,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
CoinRecord,
|
||||
@ -58,18 +57,18 @@ import {
|
||||
PlanchetRecord,
|
||||
WithdrawalGroupRecord,
|
||||
} from "../db.js";
|
||||
import { walletCoreDebugFlags } from "../util/debugFlags.js";
|
||||
import {
|
||||
HttpRequestLibrary,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
} from "../util/http.js";
|
||||
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
|
||||
import {
|
||||
getErrorDetailFromException,
|
||||
makeErrorDetail,
|
||||
TalerError,
|
||||
} from "../errors.js";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import { walletCoreDebugFlags } from "../util/debugFlags.js";
|
||||
import {
|
||||
HttpRequestLibrary,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
} from "../util/http.js";
|
||||
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
|
||||
import {
|
||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
||||
|
@ -92,3 +92,18 @@ export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
|
||||
updateRetryInfoTimeout(info, p);
|
||||
return info;
|
||||
}
|
||||
|
||||
export namespace RetryInfo {
|
||||
export function increment(
|
||||
r: RetryInfo | undefined,
|
||||
p: RetryPolicy = defaultRetryPolicy,
|
||||
) {
|
||||
if (!r) {
|
||||
return initRetryInfo(p);
|
||||
}
|
||||
const r2 = { ...r };
|
||||
r2.retryCounter++;
|
||||
updateRetryInfoTimeout(r2, p);
|
||||
return r2;
|
||||
}
|
||||
}
|
||||
|
@ -193,10 +193,7 @@ import {
|
||||
import { DbAccess, GetReadWriteAccess } from "./util/query.js";
|
||||
import { TimerGroup } from "./util/timer.js";
|
||||
import { WalletCoreApiClient } from "./wallet-api-types.js";
|
||||
import {
|
||||
TalerCryptoInterface,
|
||||
TalerCryptoInterfaceR,
|
||||
} from "./crypto/cryptoImplementation.js";
|
||||
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
||||
|
||||
const builtinAuditors: AuditorTrustRecord[] = [
|
||||
{
|
||||
|
@ -22,7 +22,6 @@ import {
|
||||
PaytoUri,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits";
|
||||
import { saturate } from "polished";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { Loading } from "../components/Loading";
|
||||
@ -30,7 +29,6 @@ import { LoadingError } from "../components/LoadingError";
|
||||
import { SelectList } from "../components/SelectList";
|
||||
import {
|
||||
Button,
|
||||
ButtonBoxWarning,
|
||||
ButtonPrimary,
|
||||
ErrorText,
|
||||
Input,
|
||||
|
@ -193,7 +193,6 @@ importers:
|
||||
'@rollup/plugin-replace': ^3.0.1
|
||||
'@types/node': ^17.0.17
|
||||
axios: ^0.25.0
|
||||
cancellationtoken: ^2.2.0
|
||||
prettier: ^2.5.1
|
||||
rimraf: ^3.0.2
|
||||
rollup: ^2.67.2
|
||||
@ -207,7 +206,6 @@ importers:
|
||||
'@gnu-taler/taler-util': link:../taler-util
|
||||
'@gnu-taler/taler-wallet-core': link:../taler-wallet-core
|
||||
axios: 0.25.0
|
||||
cancellationtoken: 2.2.0
|
||||
source-map-support: 0.5.21
|
||||
tslib: 2.3.1
|
||||
devDependencies:
|
||||
@ -8726,10 +8724,6 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/cancellationtoken/2.2.0:
|
||||
resolution: {integrity: sha512-uF4sHE5uh2VdEZtIRJKGoXAD9jm7bFY0tDRCzH4iLp262TOJ2lrtNHjMG2zc8H+GICOpELIpM7CGW5JeWnb3Hg==}
|
||||
dev: false
|
||||
|
||||
/caniuse-api/3.0.0:
|
||||
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user