implement backup scheduling, other tweaks
This commit is contained in:
parent
3603a68669
commit
42fe576320
@ -50,6 +50,7 @@ export enum NotificationType {
|
||||
RefundApplyOperationError = "refund-apply-error",
|
||||
RefundStatusOperationError = "refund-status-error",
|
||||
ProposalOperationError = "proposal-error",
|
||||
BackupOperationError = "backup-error",
|
||||
TipOperationError = "tip-error",
|
||||
PayOperationError = "pay-error",
|
||||
PayOperationSuccess = "pay-operation-success",
|
||||
@ -159,6 +160,11 @@ export interface RefreshOperationErrorNotification {
|
||||
error: TalerErrorDetails;
|
||||
}
|
||||
|
||||
export interface BackupOperationErrorNotification {
|
||||
type: NotificationType.BackupOperationError;
|
||||
error: TalerErrorDetails;
|
||||
}
|
||||
|
||||
export interface RefundStatusOperationErrorNotification {
|
||||
type: NotificationType.RefundStatusOperationError;
|
||||
error: TalerErrorDetails;
|
||||
@ -234,6 +240,7 @@ export interface PayOperationSuccessNotification {
|
||||
}
|
||||
|
||||
export type WalletNotification =
|
||||
| BackupOperationErrorNotification
|
||||
| WithdrawOperationErrorNotification
|
||||
| ReserveOperationErrorNotification
|
||||
| ExchangeOperationErrorNotification
|
||||
|
@ -1552,11 +1552,26 @@ export interface RecoupGroupRecord {
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
}
|
||||
|
||||
export enum BackupProviderStatus {
|
||||
PaymentRequired = "payment-required",
|
||||
export enum BackupProviderStateTag {
|
||||
Provisional = "provisional",
|
||||
Ready = "ready",
|
||||
Retrying = "retrying",
|
||||
}
|
||||
|
||||
export type BackupProviderState =
|
||||
| {
|
||||
tag: BackupProviderStateTag.Provisional;
|
||||
}
|
||||
| {
|
||||
tag: BackupProviderStateTag.Ready;
|
||||
nextBackupTimestamp: Timestamp;
|
||||
}
|
||||
| {
|
||||
tag: BackupProviderStateTag.Retrying;
|
||||
retryInfo: RetryInfo;
|
||||
lastError?: TalerErrorDetails;
|
||||
};
|
||||
|
||||
export interface BackupProviderTerms {
|
||||
supportedProtocolVersion: string;
|
||||
annualFee: AmountString;
|
||||
@ -1578,8 +1593,6 @@ export interface BackupProviderRecord {
|
||||
*/
|
||||
terms?: BackupProviderTerms;
|
||||
|
||||
active: boolean;
|
||||
|
||||
/**
|
||||
* Hash of the last encrypted backup that we already merged
|
||||
* or successfully uploaded ourselves.
|
||||
@ -1599,6 +1612,8 @@ export interface BackupProviderRecord {
|
||||
* Proposal that we're currently trying to pay for.
|
||||
*
|
||||
* (Also included in paymentProposalIds.)
|
||||
*
|
||||
* FIXME: Make this part of a proper BackupProviderState?
|
||||
*/
|
||||
currentPaymentProposalId?: string;
|
||||
|
||||
@ -1610,20 +1625,7 @@ export interface BackupProviderRecord {
|
||||
*/
|
||||
paymentProposalIds: string[];
|
||||
|
||||
/**
|
||||
* Next scheduled backup.
|
||||
*/
|
||||
nextBackupTimestamp?: Timestamp;
|
||||
|
||||
/**
|
||||
* Retry info.
|
||||
*/
|
||||
retryInfo: RetryInfo;
|
||||
|
||||
/**
|
||||
* Last error that occurred, if any.
|
||||
*/
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
state: BackupProviderState;
|
||||
|
||||
/**
|
||||
* UIDs for the operation that added the backup provider.
|
||||
@ -1851,7 +1853,15 @@ export const WalletStoresV1 = {
|
||||
describeContents<BackupProviderRecord>("backupProviders", {
|
||||
keyPath: "baseUrl",
|
||||
}),
|
||||
{},
|
||||
{
|
||||
byPaymentProposalId: describeIndex(
|
||||
"byPaymentProposalId",
|
||||
"paymentProposalIds",
|
||||
{
|
||||
multiEntry: true,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
depositGroups: describeStore(
|
||||
describeContents<DepositGroupRecord>("depositGroups", {
|
||||
|
@ -263,7 +263,7 @@ export async function importBackup(
|
||||
updateClock: backupExchange.update_clock,
|
||||
},
|
||||
permanent: true,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
lastUpdate: undefined,
|
||||
nextUpdate: getTimestampNow(),
|
||||
nextRefreshCheck: getTimestampNow(),
|
||||
@ -443,7 +443,7 @@ export async function importBackup(
|
||||
timestampReserveInfoPosted:
|
||||
backupReserve.bank_info?.timestamp_reserve_info_posted,
|
||||
senderWire: backupReserve.sender_wire,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
lastError: undefined,
|
||||
lastSuccessfulStatusQuery: { t_ms: "never" },
|
||||
initialWithdrawalGroupId:
|
||||
@ -483,7 +483,7 @@ export async function importBackup(
|
||||
backupWg.raw_withdrawal_amount,
|
||||
),
|
||||
reservePub,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
secretSeed: backupWg.secret_seed,
|
||||
timestampStart: backupWg.timestamp_created,
|
||||
timestampFinish: backupWg.timestamp_finish,
|
||||
@ -593,7 +593,7 @@ export async function importBackup(
|
||||
cryptoComp.proposalNoncePrivToPub[backupProposal.nonce_priv],
|
||||
proposalId: backupProposal.proposal_id,
|
||||
repurchaseProposalId: backupProposal.repurchase_proposal_id,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
download,
|
||||
proposalStatus,
|
||||
});
|
||||
@ -728,7 +728,7 @@ export async function importBackup(
|
||||
cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
|
||||
lastPayError: undefined,
|
||||
autoRefundDeadline: { t_ms: "never" },
|
||||
refundStatusRetryInfo: initRetryInfo(false),
|
||||
refundStatusRetryInfo: initRetryInfo(),
|
||||
lastRefundStatusError: undefined,
|
||||
timestampAccept: backupPurchase.timestamp_accept,
|
||||
timestampFirstSuccessfulPay:
|
||||
@ -738,7 +738,7 @@ export async function importBackup(
|
||||
lastSessionId: undefined,
|
||||
abortStatus,
|
||||
// FIXME!
|
||||
payRetryInfo: initRetryInfo(false),
|
||||
payRetryInfo: initRetryInfo(),
|
||||
download,
|
||||
paymentSubmitPending: !backupPurchase.timestamp_first_successful_pay,
|
||||
refundQueryRequested: false,
|
||||
@ -835,7 +835,7 @@ export async function importBackup(
|
||||
Amounts.parseOrThrow(x.estimated_output_amount),
|
||||
),
|
||||
refreshSessionPerCoin,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -861,7 +861,7 @@ export async function importBackup(
|
||||
merchantBaseUrl: backupTip.exchange_base_url,
|
||||
merchantTipId: backupTip.merchant_tip_id,
|
||||
pickedUpTimestamp: backupTip.timestamp_finished,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
secretSeed: backupTip.secret_seed,
|
||||
tipAmountEffective: denomsSel.totalCoinValue,
|
||||
tipAmountRaw: Amounts.parseOrThrow(backupTip.tip_amount_raw),
|
||||
|
@ -41,6 +41,7 @@ import {
|
||||
getTimestampNow,
|
||||
j2s,
|
||||
Logger,
|
||||
NotificationType,
|
||||
PreparePayResultType,
|
||||
RecoveryLoadRequest,
|
||||
RecoveryMergeStrategy,
|
||||
@ -71,11 +72,15 @@ import {
|
||||
import { CryptoApi } from "../../crypto/workers/cryptoApi.js";
|
||||
import {
|
||||
BackupProviderRecord,
|
||||
BackupProviderState,
|
||||
BackupProviderStateTag,
|
||||
BackupProviderTerms,
|
||||
ConfigRecord,
|
||||
WalletBackupConfState,
|
||||
WalletStoresV1,
|
||||
WALLET_BACKUP_STATE_KEY,
|
||||
} from "../../db.js";
|
||||
import { guardOperationException } from "../../errors.js";
|
||||
import {
|
||||
HttpResponseStatus,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
@ -85,7 +90,8 @@ import {
|
||||
checkDbInvariant,
|
||||
checkLogicInvariant,
|
||||
} from "../../util/invariants.js";
|
||||
import { initRetryInfo } from "../../util/retries.js";
|
||||
import { GetReadWriteAccess } from "../../util/query.js";
|
||||
import { initRetryInfo, updateRetryInfoTimeout } from "../../util/retries.js";
|
||||
import {
|
||||
checkPaymentByProposalId,
|
||||
confirmPay,
|
||||
@ -247,6 +253,14 @@ interface BackupForProviderArgs {
|
||||
retryAfterPayment: boolean;
|
||||
}
|
||||
|
||||
function getNextBackupTimestamp(): Timestamp {
|
||||
// FIXME: Randomize!
|
||||
return timestampAddDuration(
|
||||
getTimestampNow(),
|
||||
durationFromSpec({ minutes: 5 }),
|
||||
);
|
||||
}
|
||||
|
||||
async function runBackupCycleForProvider(
|
||||
ws: InternalWalletState,
|
||||
args: BackupForProviderArgs,
|
||||
@ -304,8 +318,11 @@ async function runBackupCycleForProvider(
|
||||
if (!prov) {
|
||||
return;
|
||||
}
|
||||
delete prov.lastError;
|
||||
prov.lastBackupCycleTimestamp = getTimestampNow();
|
||||
prov.state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getNextBackupTimestamp(),
|
||||
};
|
||||
await tx.backupProvider.put(prov);
|
||||
});
|
||||
return;
|
||||
@ -345,7 +362,9 @@ async function runBackupCycleForProvider(
|
||||
ids.add(proposalId);
|
||||
provRec.paymentProposalIds = Array.from(ids).sort();
|
||||
provRec.currentPaymentProposalId = proposalId;
|
||||
// FIXME: allocate error code for this!
|
||||
await tx.backupProviders.put(provRec);
|
||||
await incrementBackupRetryInTx(tx, args.provider.baseUrl, undefined);
|
||||
});
|
||||
|
||||
if (doPay) {
|
||||
@ -376,7 +395,10 @@ async function runBackupCycleForProvider(
|
||||
}
|
||||
prov.lastBackupHash = encodeCrock(currentBackupHash);
|
||||
prov.lastBackupCycleTimestamp = getTimestampNow();
|
||||
prov.lastError = undefined;
|
||||
prov.state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getNextBackupTimestamp(),
|
||||
};
|
||||
await tx.backupProviders.put(prov);
|
||||
});
|
||||
return;
|
||||
@ -397,11 +419,19 @@ async function runBackupCycleForProvider(
|
||||
return;
|
||||
}
|
||||
prov.lastBackupHash = encodeCrock(hash(backupEnc));
|
||||
prov.lastBackupCycleTimestamp = getTimestampNow();
|
||||
prov.lastError = undefined;
|
||||
// FIXME: Allocate error code for this situation?
|
||||
prov.state = {
|
||||
tag: BackupProviderStateTag.Retrying,
|
||||
retryInfo: initRetryInfo(),
|
||||
};
|
||||
await tx.backupProvider.put(prov);
|
||||
});
|
||||
logger.info("processed existing backup");
|
||||
// Now upload our own, merged backup.
|
||||
await runBackupCycleForProvider(ws, {
|
||||
...args,
|
||||
retryAfterPayment: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -412,15 +442,82 @@ async function runBackupCycleForProvider(
|
||||
const err = await readTalerErrorResponse(resp);
|
||||
logger.error(`got error response from backup provider: ${j2s(err)}`);
|
||||
await ws.db
|
||||
.mktx((x) => ({ backupProvider: x.backupProviders }))
|
||||
.mktx((x) => ({ backupProviders: x.backupProviders }))
|
||||
.runReadWrite(async (tx) => {
|
||||
const prov = await tx.backupProvider.get(provider.baseUrl);
|
||||
if (!prov) {
|
||||
incrementBackupRetryInTx(tx, args.provider.baseUrl, err);
|
||||
});
|
||||
}
|
||||
|
||||
async function incrementBackupRetryInTx(
|
||||
tx: GetReadWriteAccess<{
|
||||
backupProviders: typeof WalletStoresV1.backupProviders;
|
||||
}>,
|
||||
backupProviderBaseUrl: string,
|
||||
err: TalerErrorDetails | undefined,
|
||||
): Promise<void> {
|
||||
const pr = await tx.backupProviders.get(backupProviderBaseUrl);
|
||||
if (!pr) {
|
||||
return;
|
||||
}
|
||||
prov.lastError = err;
|
||||
await tx.backupProvider.put(prov);
|
||||
if (pr.state.tag === BackupProviderStateTag.Retrying) {
|
||||
pr.state.retryInfo.retryCounter++;
|
||||
pr.state.lastError = err;
|
||||
updateRetryInfoTimeout(pr.state.retryInfo);
|
||||
} else if (pr.state.tag === BackupProviderStateTag.Ready) {
|
||||
pr.state = {
|
||||
tag: BackupProviderStateTag.Retrying,
|
||||
retryInfo: initRetryInfo(),
|
||||
lastError: err,
|
||||
};
|
||||
}
|
||||
await tx.backupProviders.put(pr);
|
||||
}
|
||||
|
||||
async function incrementBackupRetry(
|
||||
ws: InternalWalletState,
|
||||
backupProviderBaseUrl: string,
|
||||
err: TalerErrorDetails | undefined,
|
||||
): Promise<void> {
|
||||
await ws.db
|
||||
.mktx((x) => ({ backupProviders: x.backupProviders }))
|
||||
.runReadWrite(async (tx) =>
|
||||
incrementBackupRetryInTx(tx, backupProviderBaseUrl, err),
|
||||
);
|
||||
}
|
||||
|
||||
export async function processBackupForProvider(
|
||||
ws: InternalWalletState,
|
||||
backupProviderBaseUrl: string,
|
||||
): Promise<void> {
|
||||
const provider = await ws.db
|
||||
.mktx((x) => ({ backupProviders: x.backupProviders }))
|
||||
.runReadOnly(async (tx) => {
|
||||
return await tx.backupProviders.get(backupProviderBaseUrl);
|
||||
});
|
||||
if (!provider) {
|
||||
throw Error("unknown backup provider");
|
||||
}
|
||||
|
||||
const onOpErr = (err: TalerErrorDetails): Promise<void> =>
|
||||
incrementBackupRetry(ws, backupProviderBaseUrl, err);
|
||||
|
||||
const run = async () => {
|
||||
const backupJson = await exportBackup(ws);
|
||||
const backupConfig = await provideBackupState(ws);
|
||||
const encBackup = await encryptBackup(backupConfig, backupJson);
|
||||
const currentBackupHash = hash(encBackup);
|
||||
|
||||
await runBackupCycleForProvider(ws, {
|
||||
provider,
|
||||
backupJson,
|
||||
backupConfig,
|
||||
encBackup,
|
||||
currentBackupHash,
|
||||
retryAfterPayment: true,
|
||||
});
|
||||
};
|
||||
|
||||
await guardOperationException(run, onOpErr);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -436,14 +533,9 @@ export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
||||
.runReadOnly(async (tx) => {
|
||||
return await tx.backupProviders.iter().toArray();
|
||||
});
|
||||
logger.trace("got backup providers", providers);
|
||||
const backupJson = await exportBackup(ws);
|
||||
|
||||
logger.trace(`running backup cycle with backup JSON: ${j2s(backupJson)}`);
|
||||
|
||||
const backupConfig = await provideBackupState(ws);
|
||||
const encBackup = await encryptBackup(backupConfig, backupJson);
|
||||
|
||||
const currentBackupHash = hash(encBackup);
|
||||
|
||||
for (const provider of providers) {
|
||||
@ -506,7 +598,10 @@ export async function addBackupProvider(
|
||||
if (oldProv) {
|
||||
logger.info("old backup provider found");
|
||||
if (req.activate) {
|
||||
oldProv.active = true;
|
||||
oldProv.state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getTimestampNow(),
|
||||
};
|
||||
logger.info("setting existing backup provider to active");
|
||||
await tx.backupProviders.put(oldProv);
|
||||
}
|
||||
@ -522,8 +617,19 @@ export async function addBackupProvider(
|
||||
await ws.db
|
||||
.mktx((x) => ({ backupProviders: x.backupProviders }))
|
||||
.runReadWrite(async (tx) => {
|
||||
let state: BackupProviderState;
|
||||
if (req.activate) {
|
||||
state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getTimestampNow(),
|
||||
};
|
||||
} else {
|
||||
state = {
|
||||
tag: BackupProviderStateTag.Provisional,
|
||||
};
|
||||
}
|
||||
await tx.backupProviders.put({
|
||||
active: !!req.activate,
|
||||
state,
|
||||
terms: {
|
||||
annualFee: terms.annual_fee,
|
||||
storageLimitInMegabytes: terms.storage_limit_in_megabytes,
|
||||
@ -531,8 +637,6 @@ export async function addBackupProvider(
|
||||
},
|
||||
paymentProposalIds: [],
|
||||
baseUrl: canonUrl,
|
||||
lastError: undefined,
|
||||
retryInfo: initRetryInfo(false),
|
||||
uids: [encodeCrock(getRandomBytes(32))],
|
||||
});
|
||||
});
|
||||
@ -697,11 +801,14 @@ export async function getBackupInfo(
|
||||
const providers: ProviderInfo[] = [];
|
||||
for (const x of providerRecords) {
|
||||
providers.push({
|
||||
active: x.active,
|
||||
active: x.state.tag !== BackupProviderStateTag.Provisional,
|
||||
syncProviderBaseUrl: x.baseUrl,
|
||||
lastSuccessfulBackupTimestamp: x.lastBackupCycleTimestamp,
|
||||
paymentProposalIds: x.paymentProposalIds,
|
||||
lastError: x.lastError,
|
||||
lastError:
|
||||
x.state.tag === BackupProviderStateTag.Retrying
|
||||
? x.state.lastError
|
||||
: undefined,
|
||||
paymentStatus: await getProviderPaymentInfo(ws, x),
|
||||
terms: x.terms,
|
||||
});
|
||||
@ -728,7 +835,7 @@ export async function getBackupRecovery(
|
||||
});
|
||||
return {
|
||||
providers: providers
|
||||
.filter((x) => x.active)
|
||||
.filter((x) => x.state.tag !== BackupProviderStateTag.Provisional)
|
||||
.map((x) => {
|
||||
return {
|
||||
url: x.baseUrl,
|
||||
@ -763,11 +870,12 @@ async function backupRecoveryTheirs(
|
||||
const existingProv = await tx.backupProviders.get(prov.url);
|
||||
if (!existingProv) {
|
||||
await tx.backupProviders.put({
|
||||
active: true,
|
||||
baseUrl: prov.url,
|
||||
paymentProposalIds: [],
|
||||
retryInfo: initRetryInfo(false),
|
||||
lastError: undefined,
|
||||
state: {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getTimestampNow(),
|
||||
},
|
||||
uids: [encodeCrock(getRandomBytes(32))],
|
||||
});
|
||||
}
|
||||
|
@ -443,7 +443,7 @@ export async function createDepositGroup(
|
||||
payto_uri: req.depositPaytoUri,
|
||||
salt: wireSalt,
|
||||
},
|
||||
retryInfo: initRetryInfo(true),
|
||||
retryInfo: initRetryInfo(),
|
||||
lastError: undefined,
|
||||
};
|
||||
|
||||
|
@ -297,7 +297,7 @@ async function provideExchangeRecord(
|
||||
r = {
|
||||
permanent: true,
|
||||
baseUrl: baseUrl,
|
||||
retryInfo: initRetryInfo(false),
|
||||
retryInfo: initRetryInfo(),
|
||||
detailsPointer: undefined,
|
||||
lastUpdate: undefined,
|
||||
nextUpdate: now,
|
||||
@ -498,7 +498,7 @@ async function updateExchangeFromUrlImpl(
|
||||
};
|
||||
// FIXME: only update if pointer got updated
|
||||
r.lastError = undefined;
|
||||
r.retryInfo = initRetryInfo(false);
|
||||
r.retryInfo = initRetryInfo();
|
||||
r.lastUpdate = getTimestampNow();
|
||||
(r.nextUpdate = keysInfo.expiry),
|
||||
// New denominations might be available.
|
||||
|
@ -77,6 +77,7 @@ import {
|
||||
AbortStatus,
|
||||
AllowedAuditorInfo,
|
||||
AllowedExchangeInfo,
|
||||
BackupProviderStateTag,
|
||||
CoinRecord,
|
||||
CoinStatus,
|
||||
DenominationRecord,
|
||||
@ -489,7 +490,7 @@ async function recordConfirmPay(
|
||||
if (p) {
|
||||
p.proposalStatus = ProposalStatus.ACCEPTED;
|
||||
delete p.lastError;
|
||||
p.retryInfo = initRetryInfo(false);
|
||||
p.retryInfo = initRetryInfo();
|
||||
await tx.proposals.put(p);
|
||||
}
|
||||
await tx.purchases.put(t);
|
||||
@ -942,7 +943,7 @@ async function storeFirstPaySuccess(
|
||||
purchase.paymentSubmitPending = false;
|
||||
purchase.lastPayError = undefined;
|
||||
purchase.lastSessionId = sessionId;
|
||||
purchase.payRetryInfo = initRetryInfo(false);
|
||||
purchase.payRetryInfo = initRetryInfo();
|
||||
purchase.merchantPaySig = paySig;
|
||||
if (isFirst) {
|
||||
const ar = purchase.download.contractData.autoRefund;
|
||||
@ -978,7 +979,7 @@ async function storePayReplaySuccess(
|
||||
}
|
||||
purchase.paymentSubmitPending = false;
|
||||
purchase.lastPayError = undefined;
|
||||
purchase.payRetryInfo = initRetryInfo(false);
|
||||
purchase.payRetryInfo = initRetryInfo();
|
||||
purchase.lastSessionId = sessionId;
|
||||
await tx.purchases.put(purchase);
|
||||
});
|
||||
@ -1100,6 +1101,26 @@ async function handleInsufficientFunds(
|
||||
});
|
||||
}
|
||||
|
||||
async function unblockBackup(
|
||||
ws: InternalWalletState,
|
||||
proposalId: string,
|
||||
): Promise<void> {
|
||||
await ws.db
|
||||
.mktx((x) => ({ backupProviders: x.backupProviders }))
|
||||
.runReadWrite(async (tx) => {
|
||||
const bp = await tx.backupProviders.indexes.byPaymentProposalId
|
||||
.iter(proposalId)
|
||||
.forEachAsync(async (bp) => {
|
||||
if (bp.state.tag === BackupProviderStateTag.Retrying) {
|
||||
bp.state = {
|
||||
tag: BackupProviderStateTag.Ready,
|
||||
nextBackupTimestamp: getTimestampNow(),
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a payment to the merchant.
|
||||
*
|
||||
@ -1228,6 +1249,7 @@ async function submitPay(
|
||||
}
|
||||
|
||||
await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp.sig);
|
||||
await unblockBackup(ws, proposalId);
|
||||
} else {
|
||||
const payAgainUrl = new URL(
|
||||
`orders/${purchase.download.contractData.orderId}/paid`,
|
||||
@ -1266,6 +1288,7 @@ async function submitPay(
|
||||
);
|
||||
}
|
||||
await storePayReplaySuccess(ws, proposalId, sessionId);
|
||||
await unblockBackup(ws, proposalId);
|
||||
}
|
||||
|
||||
ws.notify({
|
||||
|
@ -14,6 +14,10 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Derive pending tasks from the wallet database.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
@ -22,13 +26,18 @@ import {
|
||||
ReserveRecordStatus,
|
||||
AbortStatus,
|
||||
WalletStoresV1,
|
||||
BackupProviderStateTag,
|
||||
} from "../db.js";
|
||||
import {
|
||||
PendingOperationsResponse,
|
||||
PendingOperationType,
|
||||
PendingTaskType,
|
||||
ReserveType,
|
||||
} from "../pending-types.js";
|
||||
import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
getTimestampNow,
|
||||
isTimestampExpired,
|
||||
Timestamp,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { InternalWalletState } from "../common.js";
|
||||
import { getBalancesInsideTransaction } from "./balance.js";
|
||||
import { GetReadOnlyAccess } from "../util/query.js";
|
||||
@ -43,7 +52,7 @@ async function gatherExchangePending(
|
||||
): Promise<void> {
|
||||
await tx.exchanges.iter().forEachAsync(async (e) => {
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.ExchangeUpdate,
|
||||
type: PendingTaskType.ExchangeUpdate,
|
||||
givesLifeness: false,
|
||||
timestampDue: e.nextUpdate,
|
||||
exchangeBaseUrl: e.baseUrl,
|
||||
@ -51,7 +60,7 @@ async function gatherExchangePending(
|
||||
});
|
||||
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.ExchangeCheckRefresh,
|
||||
type: PendingTaskType.ExchangeCheckRefresh,
|
||||
timestampDue: e.nextRefreshCheck,
|
||||
givesLifeness: false,
|
||||
exchangeBaseUrl: e.baseUrl,
|
||||
@ -76,7 +85,7 @@ async function gatherReservePending(
|
||||
case ReserveRecordStatus.QUERYING_STATUS:
|
||||
case ReserveRecordStatus.REGISTERING_BANK:
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Reserve,
|
||||
type: PendingTaskType.Reserve,
|
||||
givesLifeness: true,
|
||||
timestampDue: reserve.retryInfo.nextRetry,
|
||||
stage: reserve.reserveStatus,
|
||||
@ -103,7 +112,7 @@ async function gatherRefreshPending(
|
||||
return;
|
||||
}
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Refresh,
|
||||
type: PendingTaskType.Refresh,
|
||||
givesLifeness: true,
|
||||
timestampDue: r.retryInfo.nextRetry,
|
||||
refreshGroupId: r.refreshGroupId,
|
||||
@ -136,7 +145,7 @@ async function gatherWithdrawalPending(
|
||||
}
|
||||
});
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Withdraw,
|
||||
type: PendingTaskType.Withdraw,
|
||||
givesLifeness: true,
|
||||
timestampDue: wsr.retryInfo.nextRetry,
|
||||
withdrawalGroupId: wsr.withdrawalGroupId,
|
||||
@ -157,7 +166,7 @@ async function gatherProposalPending(
|
||||
} else if (proposal.proposalStatus == ProposalStatus.DOWNLOADING) {
|
||||
const timestampDue = proposal.retryInfo?.nextRetry ?? getTimestampNow();
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.ProposalDownload,
|
||||
type: PendingTaskType.ProposalDownload,
|
||||
givesLifeness: true,
|
||||
timestampDue,
|
||||
merchantBaseUrl: proposal.merchantBaseUrl,
|
||||
@ -182,7 +191,7 @@ async function gatherTipPending(
|
||||
}
|
||||
if (tip.acceptedTimestamp) {
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.TipPickup,
|
||||
type: PendingTaskType.TipPickup,
|
||||
givesLifeness: true,
|
||||
timestampDue: tip.retryInfo.nextRetry,
|
||||
merchantBaseUrl: tip.merchantBaseUrl,
|
||||
@ -202,7 +211,7 @@ async function gatherPurchasePending(
|
||||
if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) {
|
||||
const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow();
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Pay,
|
||||
type: PendingTaskType.Pay,
|
||||
givesLifeness: true,
|
||||
timestampDue,
|
||||
isReplay: false,
|
||||
@ -213,7 +222,7 @@ async function gatherPurchasePending(
|
||||
}
|
||||
if (pr.refundQueryRequested) {
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.RefundQuery,
|
||||
type: PendingTaskType.RefundQuery,
|
||||
givesLifeness: true,
|
||||
timestampDue: pr.refundStatusRetryInfo.nextRetry,
|
||||
proposalId: pr.proposalId,
|
||||
@ -234,7 +243,7 @@ async function gatherRecoupPending(
|
||||
return;
|
||||
}
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Recoup,
|
||||
type: PendingTaskType.Recoup,
|
||||
givesLifeness: true,
|
||||
timestampDue: rg.retryInfo.nextRetry,
|
||||
recoupGroupId: rg.recoupGroupId,
|
||||
@ -244,23 +253,32 @@ async function gatherRecoupPending(
|
||||
});
|
||||
}
|
||||
|
||||
async function gatherDepositPending(
|
||||
tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>,
|
||||
async function gatherBackupPending(
|
||||
tx: GetReadOnlyAccess<{
|
||||
backupProviders: typeof WalletStoresV1.backupProviders;
|
||||
}>,
|
||||
now: Timestamp,
|
||||
resp: PendingOperationsResponse,
|
||||
): Promise<void> {
|
||||
await tx.depositGroups.iter().forEach((dg) => {
|
||||
if (dg.timestampFinished) {
|
||||
return;
|
||||
}
|
||||
await tx.backupProviders.iter().forEach((bp) => {
|
||||
if (bp.state.tag === BackupProviderStateTag.Ready) {
|
||||
resp.pendingOperations.push({
|
||||
type: PendingOperationType.Deposit,
|
||||
givesLifeness: true,
|
||||
timestampDue: dg.retryInfo.nextRetry,
|
||||
depositGroupId: dg.depositGroupId,
|
||||
retryInfo: dg.retryInfo,
|
||||
lastError: dg.lastError,
|
||||
type: PendingTaskType.Backup,
|
||||
givesLifeness: false,
|
||||
timestampDue: bp.state.nextBackupTimestamp,
|
||||
backupProviderBaseUrl: bp.baseUrl,
|
||||
lastError: undefined,
|
||||
});
|
||||
} else if (bp.state.tag === BackupProviderStateTag.Retrying) {
|
||||
resp.pendingOperations.push({
|
||||
type: PendingTaskType.Backup,
|
||||
givesLifeness: false,
|
||||
timestampDue: bp.state.retryInfo.nextRetry,
|
||||
backupProviderBaseUrl: bp.baseUrl,
|
||||
retryInfo: bp.state.retryInfo,
|
||||
lastError: bp.state.lastError,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -270,6 +288,7 @@ export async function getPendingOperations(
|
||||
const now = getTimestampNow();
|
||||
return await ws.db
|
||||
.mktx((x) => ({
|
||||
backupProviders: x.backupProviders,
|
||||
exchanges: x.exchanges,
|
||||
exchangeDetails: x.exchangeDetails,
|
||||
reserves: x.reserves,
|
||||
@ -297,7 +316,7 @@ export async function getPendingOperations(
|
||||
await gatherTipPending(tx, now, resp);
|
||||
await gatherPurchasePending(tx, now, resp);
|
||||
await gatherRecoupPending(tx, now, resp);
|
||||
await gatherDepositPending(tx, now, resp);
|
||||
await gatherBackupPending(tx, now, resp);
|
||||
return resp;
|
||||
});
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ async function putGroupAsFinished(
|
||||
if (allFinished) {
|
||||
logger.trace("all recoups of recoup group are finished");
|
||||
recoupGroup.timestampFinished = getTimestampNow();
|
||||
recoupGroup.retryInfo = initRetryInfo(false);
|
||||
recoupGroup.retryInfo = initRetryInfo();
|
||||
recoupGroup.lastError = undefined;
|
||||
if (recoupGroup.scheduleRefreshCoins.length > 0) {
|
||||
const refreshGroupId = await createRefreshGroup(
|
||||
|
@ -203,7 +203,7 @@ async function refreshCreateSession(
|
||||
}
|
||||
if (allDone) {
|
||||
rg.timestampFinished = getTimestampNow();
|
||||
rg.retryInfo = initRetryInfo(false);
|
||||
rg.retryInfo = initRetryInfo();
|
||||
}
|
||||
await tx.refreshGroups.put(rg);
|
||||
});
|
||||
@ -590,7 +590,7 @@ async function refreshReveal(
|
||||
}
|
||||
if (allDone) {
|
||||
rg.timestampFinished = getTimestampNow();
|
||||
rg.retryInfo = initRetryInfo(false);
|
||||
rg.retryInfo = initRetryInfo();
|
||||
}
|
||||
for (const coin of coins) {
|
||||
await tx.coins.put(coin);
|
||||
|
@ -405,7 +405,7 @@ async function acceptRefunds(
|
||||
if (queryDone) {
|
||||
p.timestampLastRefundStatus = now;
|
||||
p.lastRefundStatusError = undefined;
|
||||
p.refundStatusRetryInfo = initRetryInfo(false);
|
||||
p.refundStatusRetryInfo = initRetryInfo();
|
||||
p.refundQueryRequested = false;
|
||||
if (p.abortStatus === AbortStatus.AbortRefund) {
|
||||
p.abortStatus = AbortStatus.AbortFinished;
|
||||
@ -768,7 +768,7 @@ export async function abortFailedPayWithRefund(
|
||||
purchase.paymentSubmitPending = false;
|
||||
purchase.abortStatus = AbortStatus.AbortRefund;
|
||||
purchase.lastPayError = undefined;
|
||||
purchase.payRetryInfo = initRetryInfo(false);
|
||||
purchase.payRetryInfo = initRetryInfo();
|
||||
await tx.purchases.put(purchase);
|
||||
});
|
||||
processPurchaseQueryRefund(ws, proposalId, true).catch((e) => {
|
||||
|
@ -651,7 +651,7 @@ async function updateReserve(
|
||||
if (denomSelInfo.selectedDenoms.length === 0) {
|
||||
newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
|
||||
newReserve.lastError = undefined;
|
||||
newReserve.retryInfo = initRetryInfo(false);
|
||||
newReserve.retryInfo = initRetryInfo();
|
||||
await tx.reserves.put(newReserve);
|
||||
return;
|
||||
}
|
||||
@ -679,7 +679,7 @@ async function updateReserve(
|
||||
};
|
||||
|
||||
newReserve.lastError = undefined;
|
||||
newReserve.retryInfo = initRetryInfo(false);
|
||||
newReserve.retryInfo = initRetryInfo();
|
||||
newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
|
||||
|
||||
await tx.reserves.put(newReserve);
|
||||
|
@ -388,7 +388,7 @@ async function processTipImpl(
|
||||
}
|
||||
tr.pickedUpTimestamp = getTimestampNow();
|
||||
tr.lastError = undefined;
|
||||
tr.retryInfo = initRetryInfo(false);
|
||||
tr.retryInfo = initRetryInfo();
|
||||
await tx.tips.put(tr);
|
||||
for (const cr of newCoinRecords) {
|
||||
await tx.coins.put(cr);
|
||||
|
@ -875,7 +875,7 @@ async function processWithdrawGroupImpl(
|
||||
finishedForFirstTime = true;
|
||||
wg.timestampFinish = getTimestampNow();
|
||||
wg.lastError = undefined;
|
||||
wg.retryInfo = initRetryInfo(false);
|
||||
wg.retryInfo = initRetryInfo();
|
||||
}
|
||||
|
||||
await tx.withdrawalGroups.put(wg);
|
||||
|
@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Type and schema definitions for pending operations in the wallet.
|
||||
* Type and schema definitions for pending tasks in the wallet.
|
||||
*
|
||||
* These are only used internally, and are not part of the public
|
||||
* These are only used internally, and are not part of the stable public
|
||||
* interface to the wallet.
|
||||
*/
|
||||
|
||||
@ -32,7 +32,7 @@ import {
|
||||
import { ReserveRecordStatus } from "./db.js";
|
||||
import { RetryInfo } from "./util/retries.js";
|
||||
|
||||
export enum PendingOperationType {
|
||||
export enum PendingTaskType {
|
||||
ExchangeUpdate = "exchange-update",
|
||||
ExchangeCheckRefresh = "exchange-check-refresh",
|
||||
Pay = "pay",
|
||||
@ -45,31 +45,39 @@ export enum PendingOperationType {
|
||||
TipPickup = "tip-pickup",
|
||||
Withdraw = "withdraw",
|
||||
Deposit = "deposit",
|
||||
Backup = "backup",
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a pending operation.
|
||||
*/
|
||||
export type PendingOperationInfo = PendingOperationInfoCommon &
|
||||
export type PendingTaskInfo = PendingTaskInfoCommon &
|
||||
(
|
||||
| PendingExchangeUpdateOperation
|
||||
| PendingExchangeCheckRefreshOperation
|
||||
| PendingPayOperation
|
||||
| PendingProposalDownloadOperation
|
||||
| PendingRefreshOperation
|
||||
| PendingRefundQueryOperation
|
||||
| PendingReserveOperation
|
||||
| PendingTipPickupOperation
|
||||
| PendingWithdrawOperation
|
||||
| PendingRecoupOperation
|
||||
| PendingDepositOperation
|
||||
| PendingExchangeUpdateTask
|
||||
| PendingExchangeCheckRefreshTask
|
||||
| PendingPayTask
|
||||
| PendingProposalDownloadTask
|
||||
| PendingRefreshTask
|
||||
| PendingRefundQueryTask
|
||||
| PendingReserveTask
|
||||
| PendingTipPickupTask
|
||||
| PendingWithdrawTask
|
||||
| PendingRecoupTask
|
||||
| PendingDepositTask
|
||||
| PendingBackupTask
|
||||
);
|
||||
|
||||
export interface PendingBackupTask {
|
||||
type: PendingTaskType.Backup;
|
||||
backupProviderBaseUrl: string;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wallet is currently updating information about an exchange.
|
||||
*/
|
||||
export interface PendingExchangeUpdateOperation {
|
||||
type: PendingOperationType.ExchangeUpdate;
|
||||
export interface PendingExchangeUpdateTask {
|
||||
type: PendingTaskType.ExchangeUpdate;
|
||||
exchangeBaseUrl: string;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
}
|
||||
@ -78,8 +86,8 @@ export interface PendingExchangeUpdateOperation {
|
||||
* The wallet should check whether coins from this exchange
|
||||
* need to be auto-refreshed.
|
||||
*/
|
||||
export interface PendingExchangeCheckRefreshOperation {
|
||||
type: PendingOperationType.ExchangeCheckRefresh;
|
||||
export interface PendingExchangeCheckRefreshTask {
|
||||
type: PendingTaskType.ExchangeCheckRefresh;
|
||||
exchangeBaseUrl: string;
|
||||
}
|
||||
|
||||
@ -100,8 +108,8 @@ export enum ReserveType {
|
||||
* Does *not* include the withdrawal operation that might result
|
||||
* from this.
|
||||
*/
|
||||
export interface PendingReserveOperation {
|
||||
type: PendingOperationType.Reserve;
|
||||
export interface PendingReserveTask {
|
||||
type: PendingTaskType.Reserve;
|
||||
retryInfo: RetryInfo | undefined;
|
||||
stage: ReserveRecordStatus;
|
||||
timestampCreated: Timestamp;
|
||||
@ -113,8 +121,8 @@ export interface PendingReserveOperation {
|
||||
/**
|
||||
* Status of an ongoing withdrawal operation.
|
||||
*/
|
||||
export interface PendingRefreshOperation {
|
||||
type: PendingOperationType.Refresh;
|
||||
export interface PendingRefreshTask {
|
||||
type: PendingTaskType.Refresh;
|
||||
lastError?: TalerErrorDetails;
|
||||
refreshGroupId: string;
|
||||
finishedPerCoin: boolean[];
|
||||
@ -124,8 +132,8 @@ export interface PendingRefreshOperation {
|
||||
/**
|
||||
* Status of downloading signed contract terms from a merchant.
|
||||
*/
|
||||
export interface PendingProposalDownloadOperation {
|
||||
type: PendingOperationType.ProposalDownload;
|
||||
export interface PendingProposalDownloadTask {
|
||||
type: PendingTaskType.ProposalDownload;
|
||||
merchantBaseUrl: string;
|
||||
proposalTimestamp: Timestamp;
|
||||
proposalId: string;
|
||||
@ -139,7 +147,7 @@ export interface PendingProposalDownloadOperation {
|
||||
* proposed contract terms.
|
||||
*/
|
||||
export interface PendingProposalChoiceOperation {
|
||||
type: PendingOperationType.ProposalChoice;
|
||||
type: PendingTaskType.ProposalChoice;
|
||||
merchantBaseUrl: string;
|
||||
proposalTimestamp: Timestamp;
|
||||
proposalId: string;
|
||||
@ -148,8 +156,8 @@ export interface PendingProposalChoiceOperation {
|
||||
/**
|
||||
* The wallet is picking up a tip that the user has accepted.
|
||||
*/
|
||||
export interface PendingTipPickupOperation {
|
||||
type: PendingOperationType.TipPickup;
|
||||
export interface PendingTipPickupTask {
|
||||
type: PendingTaskType.TipPickup;
|
||||
tipId: string;
|
||||
merchantBaseUrl: string;
|
||||
merchantTipId: string;
|
||||
@ -159,8 +167,8 @@ export interface PendingTipPickupOperation {
|
||||
* The wallet is signing coins and then sending them to
|
||||
* the merchant.
|
||||
*/
|
||||
export interface PendingPayOperation {
|
||||
type: PendingOperationType.Pay;
|
||||
export interface PendingPayTask {
|
||||
type: PendingTaskType.Pay;
|
||||
proposalId: string;
|
||||
isReplay: boolean;
|
||||
retryInfo?: RetryInfo;
|
||||
@ -171,15 +179,15 @@ export interface PendingPayOperation {
|
||||
* The wallet is querying the merchant about whether any refund
|
||||
* permissions are available for a purchase.
|
||||
*/
|
||||
export interface PendingRefundQueryOperation {
|
||||
type: PendingOperationType.RefundQuery;
|
||||
export interface PendingRefundQueryTask {
|
||||
type: PendingTaskType.RefundQuery;
|
||||
proposalId: string;
|
||||
retryInfo: RetryInfo;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
}
|
||||
|
||||
export interface PendingRecoupOperation {
|
||||
type: PendingOperationType.Recoup;
|
||||
export interface PendingRecoupTask {
|
||||
type: PendingTaskType.Recoup;
|
||||
recoupGroupId: string;
|
||||
retryInfo: RetryInfo;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
@ -188,8 +196,8 @@ export interface PendingRecoupOperation {
|
||||
/**
|
||||
* Status of an ongoing withdrawal operation.
|
||||
*/
|
||||
export interface PendingWithdrawOperation {
|
||||
type: PendingOperationType.Withdraw;
|
||||
export interface PendingWithdrawTask {
|
||||
type: PendingTaskType.Withdraw;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
retryInfo: RetryInfo;
|
||||
withdrawalGroupId: string;
|
||||
@ -198,8 +206,8 @@ export interface PendingWithdrawOperation {
|
||||
/**
|
||||
* Status of an ongoing deposit operation.
|
||||
*/
|
||||
export interface PendingDepositOperation {
|
||||
type: PendingOperationType.Deposit;
|
||||
export interface PendingDepositTask {
|
||||
type: PendingTaskType.Deposit;
|
||||
lastError: TalerErrorDetails | undefined;
|
||||
retryInfo: RetryInfo;
|
||||
depositGroupId: string;
|
||||
@ -208,11 +216,11 @@ export interface PendingDepositOperation {
|
||||
/**
|
||||
* Fields that are present in every pending operation.
|
||||
*/
|
||||
export interface PendingOperationInfoCommon {
|
||||
export interface PendingTaskInfoCommon {
|
||||
/**
|
||||
* Type of the pending operation.
|
||||
*/
|
||||
type: PendingOperationType;
|
||||
type: PendingTaskType;
|
||||
|
||||
/**
|
||||
* Set to true if the operation indicates that something is really in progress,
|
||||
@ -239,7 +247,7 @@ export interface PendingOperationsResponse {
|
||||
/**
|
||||
* List of pending operations.
|
||||
*/
|
||||
pendingOperations: PendingOperationInfo[];
|
||||
pendingOperations: PendingTaskInfo[];
|
||||
|
||||
/**
|
||||
* Current wallet balance, including pending balances.
|
||||
|
@ -72,13 +72,11 @@ export function getRetryDuration(
|
||||
}
|
||||
|
||||
export function initRetryInfo(
|
||||
active = true,
|
||||
p: RetryPolicy = defaultRetryPolicy,
|
||||
): RetryInfo {
|
||||
const now = getTimestampNow();
|
||||
const info = {
|
||||
firstTry: now,
|
||||
active: true,
|
||||
nextRetry: now,
|
||||
retryCounter: 0,
|
||||
};
|
||||
|
@ -44,6 +44,7 @@ import {
|
||||
getBackupInfo,
|
||||
getBackupRecovery,
|
||||
loadBackupRecovery,
|
||||
processBackupForProvider,
|
||||
runBackupCycle,
|
||||
} from "./operations/backup/index.js";
|
||||
import { exportBackup } from "./operations/backup/export.js";
|
||||
@ -118,9 +119,9 @@ import {
|
||||
} from "./db.js";
|
||||
import { NotificationType } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
PendingOperationInfo,
|
||||
PendingTaskInfo,
|
||||
PendingOperationsResponse,
|
||||
PendingOperationType,
|
||||
PendingTaskType,
|
||||
} from "./pending-types.js";
|
||||
import { CoinDumpJson } from "@gnu-taler/taler-util";
|
||||
import { codecForTransactionsRequest } from "@gnu-taler/taler-util";
|
||||
@ -206,44 +207,47 @@ async function getWithdrawalDetailsForAmount(
|
||||
*/
|
||||
async function processOnePendingOperation(
|
||||
ws: InternalWalletState,
|
||||
pending: PendingOperationInfo,
|
||||
pending: PendingTaskInfo,
|
||||
forceNow = false,
|
||||
): Promise<void> {
|
||||
logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`);
|
||||
switch (pending.type) {
|
||||
case PendingOperationType.ExchangeUpdate:
|
||||
case PendingTaskType.ExchangeUpdate:
|
||||
await updateExchangeFromUrl(ws, pending.exchangeBaseUrl, forceNow);
|
||||
break;
|
||||
case PendingOperationType.Refresh:
|
||||
case PendingTaskType.Refresh:
|
||||
await processRefreshGroup(ws, pending.refreshGroupId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.Reserve:
|
||||
case PendingTaskType.Reserve:
|
||||
await processReserve(ws, pending.reservePub, forceNow);
|
||||
break;
|
||||
case PendingOperationType.Withdraw:
|
||||
case PendingTaskType.Withdraw:
|
||||
await processWithdrawGroup(ws, pending.withdrawalGroupId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.ProposalDownload:
|
||||
case PendingTaskType.ProposalDownload:
|
||||
await processDownloadProposal(ws, pending.proposalId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.TipPickup:
|
||||
case PendingTaskType.TipPickup:
|
||||
await processTip(ws, pending.tipId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.Pay:
|
||||
case PendingTaskType.Pay:
|
||||
await processPurchasePay(ws, pending.proposalId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.RefundQuery:
|
||||
case PendingTaskType.RefundQuery:
|
||||
await processPurchaseQueryRefund(ws, pending.proposalId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.Recoup:
|
||||
case PendingTaskType.Recoup:
|
||||
await processRecoupGroup(ws, pending.recoupGroupId, forceNow);
|
||||
break;
|
||||
case PendingOperationType.ExchangeCheckRefresh:
|
||||
case PendingTaskType.ExchangeCheckRefresh:
|
||||
await autoRefresh(ws, pending.exchangeBaseUrl);
|
||||
break;
|
||||
case PendingOperationType.Deposit:
|
||||
case PendingTaskType.Deposit:
|
||||
await processDepositGroup(ws, pending.depositGroupId);
|
||||
break;
|
||||
case PendingTaskType.Backup:
|
||||
await processBackupForProvider(ws, pending.backupProviderBaseUrl);
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(pending);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user