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