wallet-core: rename OperationAttempt->TaskRun, do not allow task result values anymore

This commit is contained in:
Florian Dold 2023-06-30 16:14:58 +02:00
parent 7523ffa910
commit d4ee961387
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
17 changed files with 284 additions and 364 deletions

View File

@ -1754,6 +1754,7 @@ export interface CoreApiRequestEnvelope {
operation: string; operation: string;
args: unknown; args: unknown;
} }
export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError;
export type CoreApiMessageEnvelope = CoreApiResponse | CoreApiNotification; export type CoreApiMessageEnvelope = CoreApiResponse | CoreApiNotification;

View File

@ -94,8 +94,8 @@ import {
} from "../../util/invariants.js"; } from "../../util/invariants.js";
import { addAttentionRequest, removeAttentionRequest } from "../attention.js"; import { addAttentionRequest, removeAttentionRequest } from "../attention.js";
import { import {
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
TaskIdentifiers, TaskIdentifiers,
} from "../common.js"; } from "../common.js";
import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js"; import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js";
@ -250,7 +250,7 @@ function getNextBackupTimestamp(): TalerPreciseTimestamp {
async function runBackupCycleForProvider( async function runBackupCycleForProvider(
ws: InternalWalletState, ws: InternalWalletState,
args: BackupForProviderArgs, args: BackupForProviderArgs,
): Promise<OperationAttemptResult<unknown, { talerUri?: string }>> { ): Promise<TaskRunResult> {
const provider = await ws.db const provider = await ws.db
.mktx((x) => [x.backupProviders]) .mktx((x) => [x.backupProviders])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -259,10 +259,7 @@ async function runBackupCycleForProvider(
if (!provider) { if (!provider) {
logger.warn("provider disappeared"); logger.warn("provider disappeared");
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
const backupJson = await exportBackup(ws); const backupJson = await exportBackup(ws);
@ -333,10 +330,7 @@ async function runBackupCycleForProvider(
type: AttentionType.BackupUnpaid, type: AttentionType.BackupUnpaid,
}); });
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
if (resp.status === HttpStatusCode.PaymentRequired) { if (resp.status === HttpStatusCode.PaymentRequired) {
@ -378,10 +372,7 @@ async function runBackupCycleForProvider(
}); });
return { return {
type: OperationAttemptResultType.Pending, type: TaskRunResultType.Pending,
result: {
talerUri,
},
}; };
} }
const result = res; const result = res;
@ -415,10 +406,7 @@ async function runBackupCycleForProvider(
); );
return { return {
type: OperationAttemptResultType.Pending, type: TaskRunResultType.Pending,
result: {
talerUri,
},
}; };
} }
@ -445,8 +433,7 @@ async function runBackupCycleForProvider(
}); });
return { return {
type: OperationAttemptResultType.Finished, type: TaskRunResultType.Finished,
result: undefined,
}; };
} }
@ -487,7 +474,7 @@ 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)}`);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: err, errorDetail: err,
}; };
} }
@ -495,7 +482,7 @@ async function runBackupCycleForProvider(
export async function processBackupForProvider( export async function processBackupForProvider(
ws: InternalWalletState, ws: InternalWalletState,
backupProviderBaseUrl: string, backupProviderBaseUrl: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const provider = await ws.db const provider = await ws.db
.mktx((x) => [x.backupProviders]) .mktx((x) => [x.backupProviders])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -720,23 +707,24 @@ async function runFirstBackupCycleForProvider(
): Promise<AddBackupProviderResponse> { ): Promise<AddBackupProviderResponse> {
const resp = await runBackupCycleForProvider(ws, args); const resp = await runBackupCycleForProvider(ws, args);
switch (resp.type) { switch (resp.type) {
case OperationAttemptResultType.Error: case TaskRunResultType.Error:
throw TalerError.fromDetail( throw TalerError.fromDetail(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
resp.errorDetail as any, //FIXME create an error for backup problems resp.errorDetail as any, //FIXME create an error for backup problems
); );
case OperationAttemptResultType.Finished: case TaskRunResultType.Finished:
return { return {
status: "ok", status: "ok",
}; };
case OperationAttemptResultType.Longpoll: case TaskRunResultType.Longpoll:
throw Error( throw Error(
"unexpected runFirstBackupCycleForProvider result (longpoll)", "unexpected runFirstBackupCycleForProvider result (longpoll)",
); );
case OperationAttemptResultType.Pending: case TaskRunResultType.Pending:
return { return {
status: "payment-required", status: "payment-required",
talerUri: resp.result.talerUri, talerUri: "FIXME",
//talerUri: resp.result.talerUri,
}; };
default: default:
assertUnreachable(resp); assertUnreachable(resp);

View File

@ -433,25 +433,25 @@ async function storePendingTaskFinished(
}); });
} }
export async function runTaskWithErrorReporting<T1, T2>( export async function runTaskWithErrorReporting(
ws: InternalWalletState, ws: InternalWalletState,
opId: TaskId, opId: TaskId,
f: () => Promise<OperationAttemptResult<T1, T2>>, f: () => Promise<TaskRunResult>,
): Promise<OperationAttemptResult<T1, T2>> { ): Promise<TaskRunResult> {
let maybeError: TalerErrorDetail | undefined; let maybeError: TalerErrorDetail | undefined;
try { try {
const resp = await f(); const resp = await f();
switch (resp.type) { switch (resp.type) {
case OperationAttemptResultType.Error: case TaskRunResultType.Error:
await storePendingTaskError(ws, opId, resp.errorDetail); await storePendingTaskError(ws, opId, resp.errorDetail);
return resp; return resp;
case OperationAttemptResultType.Finished: case TaskRunResultType.Finished:
await storePendingTaskFinished(ws, opId); await storePendingTaskFinished(ws, opId);
return resp; return resp;
case OperationAttemptResultType.Pending: case TaskRunResultType.Pending:
await storePendingTaskPending(ws, opId); await storePendingTaskPending(ws, opId);
return resp; return resp;
case OperationAttemptResultType.Longpoll: case TaskRunResultType.Longpoll:
return resp; return resp;
} }
} catch (e) { } catch (e) {
@ -459,7 +459,7 @@ export async function runTaskWithErrorReporting<T1, T2>(
if (ws.stopped) { if (ws.stopped) {
logger.warn("crypto API stopped during shutdown, ignoring error"); logger.warn("crypto API stopped during shutdown, ignoring error");
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
{}, {},
@ -474,7 +474,7 @@ export async function runTaskWithErrorReporting<T1, T2>(
maybeError = e.errorDetail; maybeError = e.errorDetail;
await storePendingTaskError(ws, opId, maybeError!); await storePendingTaskError(ws, opId, maybeError!);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: e.errorDetail, errorDetail: e.errorDetail,
}; };
} else if (e instanceof Error) { } else if (e instanceof Error) {
@ -492,7 +492,7 @@ export async function runTaskWithErrorReporting<T1, T2>(
); );
await storePendingTaskError(ws, opId, maybeError); await storePendingTaskError(ws, opId, maybeError);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: maybeError, errorDetail: maybeError,
}; };
} else { } else {
@ -504,7 +504,7 @@ export async function runTaskWithErrorReporting<T1, T2>(
); );
await storePendingTaskError(ws, opId, maybeError); await storePendingTaskError(ws, opId, maybeError);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: maybeError, errorDetail: maybeError,
}; };
} }
@ -654,59 +654,55 @@ export interface TransactionManager {
abort(): Promise<void>; abort(): Promise<void>;
suspend(): Promise<void>; suspend(): Promise<void>;
resume(): Promise<void>; resume(): Promise<void>;
process(): Promise<OperationAttemptResult>; process(): Promise<TaskRunResult>;
} }
export enum OperationAttemptResultType { export enum TaskRunResultType {
Finished = "finished", Finished = "finished",
Pending = "pending", Pending = "pending",
Error = "error", Error = "error",
Longpoll = "longpoll", Longpoll = "longpoll",
} }
export type OperationAttemptResult<TSuccess = unknown, TPending = unknown> = export type TaskRunResult =
| OperationAttemptFinishedResult<TSuccess> | TaskRunFinishedResult
| OperationAttemptErrorResult | TaskRunErrorResult
| OperationAttemptLongpollResult | TaskRunLongpollResult
| OperationAttemptPendingResult<TPending>; | TaskRunPendingResult;
export namespace OperationAttemptResult { export namespace TaskRunResult {
export function finishedEmpty(): OperationAttemptResult<unknown, unknown> { export function finished(): TaskRunResult {
return { return {
type: OperationAttemptResultType.Finished, type: TaskRunResultType.Finished,
result: undefined,
}; };
} }
export function pendingEmpty(): OperationAttemptResult<unknown, unknown> { export function pending(): TaskRunResult {
return { return {
type: OperationAttemptResultType.Pending, type: TaskRunResultType.Pending,
result: undefined,
}; };
} }
export function longpoll(): OperationAttemptResult<unknown, unknown> { export function longpoll(): TaskRunResult {
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
} }
export interface OperationAttemptFinishedResult<T> { export interface TaskRunFinishedResult {
type: OperationAttemptResultType.Finished; type: TaskRunResultType.Finished;
result: T;
} }
export interface OperationAttemptPendingResult<T> { export interface TaskRunPendingResult {
type: OperationAttemptResultType.Pending; type: TaskRunResultType.Pending;
result: T;
} }
export interface OperationAttemptErrorResult { export interface TaskRunErrorResult {
type: OperationAttemptResultType.Error; type: TaskRunResultType.Error;
errorDetail: TalerErrorDetail; errorDetail: TalerErrorDetail;
} }
export interface OperationAttemptLongpollResult { export interface TaskRunLongpollResult {
type: OperationAttemptResultType.Longpoll; type: TaskRunResultType.Longpoll;
} }
export interface RetryInfo { export interface RetryInfo {
@ -942,19 +938,3 @@ export namespace TaskIdentifiers {
return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}` as TaskId; return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}` as TaskId;
} }
} }
/**
* Run an operation handler, expect a success result and extract the success value.
*/
export async function unwrapOperationHandlerResultOrThrow<T>(
res: OperationAttemptResult<T>,
): Promise<T> {
switch (res.type) {
case OperationAttemptResultType.Finished:
return res.result;
case OperationAttemptResultType.Error:
throw TalerError.fromUncheckedDetail(res.errorDetail);
default:
throw Error(`unexpected operation result (${res.type})`);
}
}

View File

@ -81,7 +81,7 @@ import { InternalWalletState } from "../internal-wallet-state.js";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import { import {
constructTaskIdentifier, constructTaskIdentifier,
OperationAttemptResult, TaskRunResult,
runLongpollAsync, runLongpollAsync,
spendCoins, spendCoins,
TombstoneTag, TombstoneTag,
@ -462,7 +462,7 @@ async function checkDepositKycStatus(
async function waitForRefreshOnDepositGroup( async function waitForRefreshOnDepositGroup(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const abortRefreshGroupId = depositGroup.abortRefreshGroupId; const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
checkLogicInvariant(!!abortRefreshGroupId); checkLogicInvariant(!!abortRefreshGroupId);
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
@ -503,13 +503,13 @@ async function waitForRefreshOnDepositGroup(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
async function refundDepositGroup( async function refundDepositGroup(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const newTxPerCoin = [...depositGroup.transactionPerCoin]; const newTxPerCoin = [...depositGroup.transactionPerCoin];
logger.info(`status per coin: ${j2s(depositGroup.transactionPerCoin)}`); logger.info(`status per coin: ${j2s(depositGroup.transactionPerCoin)}`);
for (let i = 0; i < depositGroup.transactionPerCoin.length; i++) { for (let i = 0; i < depositGroup.transactionPerCoin.length; i++) {
@ -614,13 +614,13 @@ async function refundDepositGroup(
await tx.depositGroups.put(newDg); await tx.depositGroups.put(newDg);
}); });
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
async function processDepositGroupAborting( async function processDepositGroupAborting(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info("processing deposit tx in 'aborting'"); logger.info("processing deposit tx in 'aborting'");
const abortRefreshGroupId = depositGroup.abortRefreshGroupId; const abortRefreshGroupId = depositGroup.abortRefreshGroupId;
if (!abortRefreshGroupId) { if (!abortRefreshGroupId) {
@ -634,7 +634,7 @@ async function processDepositGroupAborting(
async function processDepositGroupPendingKyc( async function processDepositGroupPendingKyc(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { depositGroupId } = depositGroup; const { depositGroupId } = depositGroup;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.Deposit, tag: TransactionType.Deposit,
@ -696,7 +696,7 @@ async function processDepositGroupPendingKyc(
); );
} }
}); });
return OperationAttemptResult.longpoll(); return TaskRunResult.longpoll();
} }
/** /**
@ -709,7 +709,7 @@ async function transitionToKycRequired(
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
kycInfo: KycPendingInfo, kycInfo: KycPendingInfo,
exchangeUrl: string, exchangeUrl: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { depositGroupId } = depositGroup; const { depositGroupId } = depositGroup;
const userType = "individual"; const userType = "individual";
@ -728,7 +728,7 @@ async function transitionToKycRequired(
}); });
if (kycStatusReq.status === HttpStatusCode.Ok) { if (kycStatusReq.status === HttpStatusCode.Ok) {
logger.warn("kyc requested, but already fulfilled"); logger.warn("kyc requested, but already fulfilled");
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else if (kycStatusReq.status === HttpStatusCode.Accepted) { } else if (kycStatusReq.status === HttpStatusCode.Accepted) {
const kycStatus = await kycStatusReq.json(); const kycStatus = await kycStatusReq.json();
logger.info(`kyc status: ${j2s(kycStatus)}`); logger.info(`kyc status: ${j2s(kycStatus)}`);
@ -754,7 +754,7 @@ async function transitionToKycRequired(
return { oldTxState, newTxState }; return { oldTxState, newTxState };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else { } else {
throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`); throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`);
} }
@ -764,7 +764,7 @@ async function processDepositGroupPendingTrack(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
cancellationToken?: CancellationToken, cancellationToken?: CancellationToken,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { depositGroupId } = depositGroup; const { depositGroupId } = depositGroup;
for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) { for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
const coinPub = depositGroup.payCoinSelection.coinPubs[i]; const coinPub = depositGroup.payCoinSelection.coinPubs[i];
@ -905,10 +905,10 @@ async function processDepositGroupPendingTrack(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
if (allWired) { if (allWired) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else { } else {
// FIXME: Use long-polling. // FIXME: Use long-polling.
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
} }
@ -916,7 +916,7 @@ async function processDepositGroupPendingDeposit(
ws: InternalWalletState, ws: InternalWalletState,
depositGroup: DepositGroupRecord, depositGroup: DepositGroupRecord,
cancellationToken?: CancellationToken, cancellationToken?: CancellationToken,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info("processing deposit group in pending(deposit)"); logger.info("processing deposit group in pending(deposit)");
const depositGroupId = depositGroup.depositGroupId; const depositGroupId = depositGroup.depositGroupId;
const contractData = extractContractData( const contractData = extractContractData(
@ -1000,7 +1000,7 @@ async function processDepositGroupPendingDeposit(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
/** /**
@ -1012,7 +1012,7 @@ export async function processDepositGroup(
options: { options: {
cancellationToken?: CancellationToken; cancellationToken?: CancellationToken;
} = {}, } = {},
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const depositGroup = await ws.db const depositGroup = await ws.db
.mktx((x) => [x.depositGroups]) .mktx((x) => [x.depositGroups])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -1020,7 +1020,7 @@ export async function processDepositGroup(
}); });
if (!depositGroup) { if (!depositGroup) {
logger.warn(`deposit group ${depositGroupId} not found`); logger.warn(`deposit group ${depositGroupId} not found`);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
switch (depositGroup.operationStatus) { switch (depositGroup.operationStatus) {
@ -1042,7 +1042,7 @@ export async function processDepositGroup(
return processDepositGroupAborting(ws, depositGroup); return processDepositGroupAborting(ws, depositGroup);
} }
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
async function getExchangeWireFee( async function getExchangeWireFee(

View File

@ -76,11 +76,10 @@ import {
} from "../util/query.js"; } from "../util/query.js";
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js"; import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js";
import { import {
OperationAttemptResult, TaskRunResultType,
OperationAttemptResultType,
runTaskWithErrorReporting, runTaskWithErrorReporting,
TaskIdentifiers, TaskIdentifiers,
unwrapOperationHandlerResultOrThrow, TaskRunResult,
} from "./common.js"; } from "./common.js";
const logger = new Logger("exchanges.ts"); const logger = new Logger("exchanges.ts");
@ -559,13 +558,34 @@ export async function updateExchangeFromUrl(
exchangeDetails: ExchangeDetailsRecord; exchangeDetails: ExchangeDetailsRecord;
}> { }> {
const canonUrl = canonicalizeBaseUrl(baseUrl); const canonUrl = canonicalizeBaseUrl(baseUrl);
return unwrapOperationHandlerResultOrThrow( const res = await runTaskWithErrorReporting(
await runTaskWithErrorReporting( ws,
ws, TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl),
TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl), () => updateExchangeFromUrlHandler(ws, canonUrl, options),
() => updateExchangeFromUrlHandler(ws, canonUrl, options),
),
); );
switch (res.type) {
case TaskRunResultType.Finished: {
const now = AbsoluteTime.now();
const { exchange, exchangeDetails } = await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails])
.runReadWrite(async (tx) => {
let exchange = await tx.exchanges.get(canonUrl);
const exchangeDetails = await getExchangeDetails(tx, baseUrl);
return { exchange, exchangeDetails };
});
if (!exchange) {
throw Error("exchange not found");
}
if (!exchangeDetails) {
throw Error("exchange details not found");
}
return { exchange, exchangeDetails };
}
case TaskRunResultType.Error:
throw TalerError.fromUncheckedDetail(res.errorDetail);
default:
throw Error(`unexpected operation result (${res.type})`);
}
} }
/** /**
@ -581,12 +601,7 @@ export async function updateExchangeFromUrlHandler(
forceNow?: boolean; forceNow?: boolean;
cancellationToken?: CancellationToken; cancellationToken?: CancellationToken;
} = {}, } = {},
): Promise< ): Promise<TaskRunResult> {
OperationAttemptResult<{
exchange: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
}>
> {
const forceNow = options.forceNow ?? false; const forceNow = options.forceNow ?? false;
logger.trace( logger.trace(
`updating exchange info for ${exchangeBaseUrl}, forced: ${forceNow}`, `updating exchange info for ${exchangeBaseUrl}, forced: ${forceNow}`,
@ -620,10 +635,7 @@ export async function updateExchangeFromUrlHandler(
} }
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: { exchange, exchangeDetails },
};
} }
logger.info("updating exchange /keys info"); logger.info("updating exchange /keys info");
@ -679,7 +691,7 @@ export async function updateExchangeFromUrlHandler(
}, },
); );
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail, errorDetail,
}; };
} }
@ -911,13 +923,7 @@ export async function updateExchangeFromUrlHandler(
}); });
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: {
exchange: updated.exchange,
exchangeDetails: updated.exchangeDetails,
},
};
} }
/** /**

View File

@ -112,8 +112,8 @@ import { checkDbInvariant } from "../util/invariants.js";
import { GetReadOnlyAccess } from "../util/query.js"; import { GetReadOnlyAccess } from "../util/query.js";
import { import {
constructTaskIdentifier, constructTaskIdentifier,
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
RetryInfo, RetryInfo,
TaskIdentifiers, TaskIdentifiers,
} from "./common.js"; } from "./common.js";
@ -325,7 +325,7 @@ export function extractContractData(
async function processDownloadProposal( async function processDownloadProposal(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const proposal = await ws.db const proposal = await ws.db
.mktx((x) => [x.purchases]) .mktx((x) => [x.purchases])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -333,17 +333,11 @@ async function processDownloadProposal(
}); });
if (!proposal) { if (!proposal) {
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) { if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) {
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
@ -560,10 +554,7 @@ async function processDownloadProposal(
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
/** /**
@ -1065,7 +1056,7 @@ export async function checkPaymentByProposalId(
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
// FIXME: What about error handling?! This doesn't properly store errors in the DB. // FIXME: What about error handling?! This doesn't properly store errors in the DB.
const r = await processPurchasePay(ws, proposalId, { forceNow: true }); const r = await processPurchasePay(ws, proposalId, { forceNow: true });
if (r.type !== OperationAttemptResultType.Finished) { if (r.type !== TaskRunResultType.Finished) {
// FIXME: This does not surface the original error // FIXME: This does not surface the original error
throw Error("submitting pay failed"); throw Error("submitting pay failed");
} }
@ -1253,7 +1244,7 @@ export async function runPayForConfirmPay(
}); });
logger.trace(`processPurchasePay response type ${res.type}`); logger.trace(`processPurchasePay response type ${res.type}`);
switch (res.type) { switch (res.type) {
case OperationAttemptResultType.Finished: { case TaskRunResultType.Finished: {
const purchase = await ws.db const purchase = await ws.db
.mktx((x) => [x.purchases]) .mktx((x) => [x.purchases])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -1272,7 +1263,7 @@ export async function runPayForConfirmPay(
}), }),
}; };
} }
case OperationAttemptResultType.Error: { case TaskRunResultType.Error: {
// We hide transient errors from the caller. // We hide transient errors from the caller.
const opRetry = await ws.db const opRetry = await ws.db
.mktx((x) => [x.operationRetries]) .mktx((x) => [x.operationRetries])
@ -1286,7 +1277,7 @@ export async function runPayForConfirmPay(
}), }),
}; };
} }
case OperationAttemptResultType.Pending: case TaskRunResultType.Pending:
logger.trace("reporting pending as confirmPay response"); logger.trace("reporting pending as confirmPay response");
return { return {
type: ConfirmPayResultType.Pending, type: ConfirmPayResultType.Pending,
@ -1296,7 +1287,7 @@ export async function runPayForConfirmPay(
}), }),
lastError: undefined, lastError: undefined,
}; };
case OperationAttemptResultType.Longpoll: case TaskRunResultType.Longpoll:
throw Error("unexpected processPurchasePay result (longpoll)"); throw Error("unexpected processPurchasePay result (longpoll)");
default: default:
assertUnreachable(res); assertUnreachable(res);
@ -1456,7 +1447,7 @@ export async function confirmPay(
export async function processPurchase( export async function processPurchase(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const purchase = await ws.db const purchase = await ws.db
.mktx((x) => [x.purchases]) .mktx((x) => [x.purchases])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -1464,7 +1455,7 @@ export async function processPurchase(
}); });
if (!purchase) { if (!purchase) {
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: { errorDetail: {
// FIXME: allocate more specific error code // FIXME: allocate more specific error code
code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
@ -1504,10 +1495,7 @@ export async function processPurchase(
case PurchaseStatus.SuspendedQueryingAutoRefund: case PurchaseStatus.SuspendedQueryingAutoRefund:
case PurchaseStatus.SuspendedQueryingRefund: case PurchaseStatus.SuspendedQueryingRefund:
case PurchaseStatus.FailedAbort: case PurchaseStatus.FailedAbort:
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
default: default:
assertUnreachable(purchase.purchaseStatus); assertUnreachable(purchase.purchaseStatus);
// throw Error(`unexpected purchase status (${purchase.purchaseStatus})`); // throw Error(`unexpected purchase status (${purchase.purchaseStatus})`);
@ -1518,7 +1506,7 @@ export async function processPurchasePay(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
options: unknown = {}, options: unknown = {},
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const purchase = await ws.db const purchase = await ws.db
.mktx((x) => [x.purchases]) .mktx((x) => [x.purchases])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -1526,7 +1514,7 @@ export async function processPurchasePay(
}); });
if (!purchase) { if (!purchase) {
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: { errorDetail: {
// FIXME: allocate more specific error code // FIXME: allocate more specific error code
code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
@ -1541,7 +1529,7 @@ export async function processPurchasePay(
case PurchaseStatus.PendingPayingReplay: case PurchaseStatus.PendingPayingReplay:
break; break;
default: default:
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
logger.trace(`processing purchase pay ${proposalId}`); logger.trace(`processing purchase pay ${proposalId}`);
@ -1589,7 +1577,7 @@ export async function processPurchasePay(
if (resp.status >= 500 && resp.status <= 599) { if (resp.status >= 500 && resp.status <= 599) {
const errDetails = await readUnexpectedResponseDetails(resp); const errDetails = await readUnexpectedResponseDetails(resp);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR, TalerErrorCode.WALLET_PAY_MERCHANT_SERVER_ERROR,
{ {
@ -1613,10 +1601,7 @@ export async function processPurchasePay(
// FIXME: Should we really consider this to be pending? // FIXME: Should we really consider this to be pending?
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Pending,
result: undefined,
};
} }
} }
@ -1677,7 +1662,7 @@ export async function processPurchasePay(
await unblockBackup(ws, proposalId); await unblockBackup(ws, proposalId);
} }
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
export async function refuseProposal( export async function refuseProposal(
@ -2114,7 +2099,7 @@ export function computePayMerchantTransactionActions(
async function processPurchaseAutoRefund( async function processPurchaseAutoRefund(
ws: InternalWalletState, ws: InternalWalletState,
purchase: PurchaseRecord, purchase: PurchaseRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const proposalId = purchase.proposalId; const proposalId = purchase.proposalId;
logger.trace(`processing auto-refund for proposal ${proposalId}`); logger.trace(`processing auto-refund for proposal ${proposalId}`);
@ -2130,7 +2115,7 @@ async function processPurchaseAutoRefund(
// FIXME: Put this logic into runLongpollAsync? // FIXME: Put this logic into runLongpollAsync?
if (ws.activeLongpoll[taskId]) { if (ws.activeLongpoll[taskId]) {
return OperationAttemptResult.longpoll(); return TaskRunResult.longpoll();
} }
const download = await expectProposalDownload(ws, purchase); const download = await expectProposalDownload(ws, purchase);
@ -2215,13 +2200,13 @@ async function processPurchaseAutoRefund(
} }
}); });
return OperationAttemptResult.longpoll(); return TaskRunResult.longpoll();
} }
async function processPurchaseAbortingRefund( async function processPurchaseAbortingRefund(
ws: InternalWalletState, ws: InternalWalletState,
purchase: PurchaseRecord, purchase: PurchaseRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const proposalId = purchase.proposalId; const proposalId = purchase.proposalId;
const download = await expectProposalDownload(ws, purchase); const download = await expectProposalDownload(ws, purchase);
logger.trace(`processing aborting-refund for proposal ${proposalId}`); logger.trace(`processing aborting-refund for proposal ${proposalId}`);
@ -2296,7 +2281,7 @@ async function processPurchaseAbortingRefund(
async function processPurchaseQueryRefund( async function processPurchaseQueryRefund(
ws: InternalWalletState, ws: InternalWalletState,
purchase: PurchaseRecord, purchase: PurchaseRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const proposalId = purchase.proposalId; const proposalId = purchase.proposalId;
logger.trace(`processing query-refund for proposal ${proposalId}`); logger.trace(`processing query-refund for proposal ${proposalId}`);
@ -2341,7 +2326,7 @@ async function processPurchaseQueryRefund(
return { oldTxState, newTxState }; return { oldTxState, newTxState };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else { } else {
const refundAwaiting = Amounts.sub( const refundAwaiting = Amounts.sub(
Amounts.parseOrThrow(orderStatus.refund_amount), Amounts.parseOrThrow(orderStatus.refund_amount),
@ -2367,14 +2352,14 @@ async function processPurchaseQueryRefund(
return { oldTxState, newTxState }; return { oldTxState, newTxState };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
} }
async function processPurchaseAcceptRefund( async function processPurchaseAcceptRefund(
ws: InternalWalletState, ws: InternalWalletState,
purchase: PurchaseRecord, purchase: PurchaseRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const proposalId = purchase.proposalId; const proposalId = purchase.proposalId;
const download = await expectProposalDownload(ws, purchase); const download = await expectProposalDownload(ws, purchase);
@ -2472,7 +2457,7 @@ async function storeRefunds(
purchase: PurchaseRecord, purchase: PurchaseRecord,
refunds: MerchantCoinRefundStatus[], refunds: MerchantCoinRefundStatus[],
reason: RefundReason, reason: RefundReason,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info(`storing refunds: ${j2s(refunds)}`); logger.info(`storing refunds: ${j2s(refunds)}`);
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
@ -2699,16 +2684,16 @@ async function storeRefunds(
}); });
if (!result) { if (!result) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
notifyTransition(ws, transactionId, result.transitionInfo); notifyTransition(ws, transactionId, result.transitionInfo);
if (result.numPendingItemsTotal > 0) { if (result.numPendingItemsTotal > 0) {
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
export function computeRefundTransactionState( export function computeRefundTransactionState(

View File

@ -66,8 +66,8 @@ import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkDbInvariant } from "../util/invariants.js"; import { checkDbInvariant } from "../util/invariants.js";
import { import {
LongpollResult, LongpollResult,
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
constructTaskIdentifier, constructTaskIdentifier,
runLongpollAsync, runLongpollAsync,
} from "./common.js"; } from "./common.js";
@ -184,7 +184,7 @@ async function longpollKycStatus(
exchangeUrl: string, exchangeUrl: string,
kycInfo: KycPendingInfo, kycInfo: KycPendingInfo,
userType: KycUserType, userType: KycUserType,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit, tag: TransactionType.PeerPullCredit,
pursePub, pursePub,
@ -242,14 +242,14 @@ async function longpollKycStatus(
} }
}); });
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
async function processPeerPullCreditAbortingDeletePurse( async function processPeerPullCreditAbortingDeletePurse(
ws: InternalWalletState, ws: InternalWalletState,
peerPullIni: PeerPullPaymentInitiationRecord, peerPullIni: PeerPullPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { pursePub, pursePriv } = peerPullIni; const { pursePub, pursePriv } = peerPullIni;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit, tag: TransactionType.PeerPushDebit,
@ -296,13 +296,13 @@ async function processPeerPullCreditAbortingDeletePurse(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
async function handlePeerPullCreditWithdrawing( async function handlePeerPullCreditWithdrawing(
ws: InternalWalletState, ws: InternalWalletState,
pullIni: PeerPullPaymentInitiationRecord, pullIni: PeerPullPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
if (!pullIni.withdrawalGroupId) { if (!pullIni.withdrawalGroupId) {
throw Error("invalid db state (withdrawing, but no withdrawal group ID"); throw Error("invalid db state (withdrawing, but no withdrawal group ID");
} }
@ -346,17 +346,17 @@ async function handlePeerPullCreditWithdrawing(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
if (finished) { if (finished) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else { } else {
// FIXME: Return indicator that we depend on the other operation! // FIXME: Return indicator that we depend on the other operation!
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
} }
async function handlePeerPullCreditCreatePurse( async function handlePeerPullCreditCreatePurse(
ws: InternalWalletState, ws: InternalWalletState,
pullIni: PeerPullPaymentInitiationRecord, pullIni: PeerPullPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount)); const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount));
const pursePub = pullIni.pursePub; const pursePub = pullIni.pursePub;
const mergeReserve = await ws.db const mergeReserve = await ws.db
@ -447,16 +447,13 @@ async function handlePeerPullCreditCreatePurse(
await tx.peerPullPaymentInitiations.put(pi2); await tx.peerPullPaymentInitiations.put(pi2);
}); });
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
export async function processPeerPullCredit( export async function processPeerPullCredit(
ws: InternalWalletState, ws: InternalWalletState,
pursePub: string, pursePub: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const pullIni = await ws.db const pullIni = await ws.db
.mktx((x) => [x.peerPullPaymentInitiations]) .mktx((x) => [x.peerPullPaymentInitiations])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -475,7 +472,7 @@ export async function processPeerPullCredit(
if (ws.activeLongpoll[retryTag]) { if (ws.activeLongpoll[retryTag]) {
logger.info("peer-pull-credit already in long-polling, returning!"); logger.info("peer-pull-credit already in long-polling, returning!");
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
@ -483,10 +480,7 @@ export async function processPeerPullCredit(
switch (pullIni.status) { switch (pullIni.status) {
case PeerPullPaymentInitiationStatus.Done: { case PeerPullPaymentInitiationStatus.Done: {
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
case PeerPullPaymentInitiationStatus.PendingReady: case PeerPullPaymentInitiationStatus.PendingReady:
runLongpollAsync(ws, retryTag, async (cancellationToken) => runLongpollAsync(ws, retryTag, async (cancellationToken) =>
@ -496,7 +490,7 @@ export async function processPeerPullCredit(
"returning early from processPeerPullCredit for long-polling in background", "returning early from processPeerPullCredit for long-polling in background",
); );
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: { case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: {
if (!pullIni.kycInfo) { if (!pullIni.kycInfo) {
@ -528,14 +522,14 @@ export async function processPeerPullCredit(
assertUnreachable(pullIni.status); assertUnreachable(pullIni.status);
} }
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
async function processPeerPullCreditKycRequired( async function processPeerPullCreditKycRequired(
ws: InternalWalletState, ws: InternalWalletState,
peerIni: PeerPullPaymentInitiationRecord, peerIni: PeerPullPaymentInitiationRecord,
kycPending: WalletKycUuid, kycPending: WalletKycUuid,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit, tag: TransactionType.PeerPullCredit,
pursePub: peerIni.pursePub, pursePub: peerIni.pursePub,
@ -560,10 +554,7 @@ async function processPeerPullCreditKycRequired(
kycStatusRes.status === HttpStatusCode.NoContent kycStatusRes.status === HttpStatusCode.NoContent
) { ) {
logger.warn("kyc requested, but already fulfilled"); logger.warn("kyc requested, but already fulfilled");
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} else if (kycStatusRes.status === HttpStatusCode.Accepted) { } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
const kycStatus = await kycStatusRes.json(); const kycStatus = await kycStatusRes.json();
logger.info(`kyc status: ${j2s(kycStatus)}`); logger.info(`kyc status: ${j2s(kycStatus)}`);
@ -574,7 +565,7 @@ async function processPeerPullCreditKycRequired(
if (!peerInc) { if (!peerInc) {
return { return {
transitionInfo: undefined, transitionInfo: undefined,
result: OperationAttemptResult.finishedEmpty(), result: TaskRunResult.finished(),
}; };
} }
const oldTxState = computePeerPullCreditTransactionState(peerInc); const oldTxState = computePeerPullCreditTransactionState(peerInc);
@ -589,8 +580,8 @@ async function processPeerPullCreditKycRequired(
await tx.peerPullPaymentInitiations.put(peerInc); await tx.peerPullPaymentInitiations.put(peerInc);
// We'll remove this eventually! New clients should rely on the // We'll remove this eventually! New clients should rely on the
// kycUrl field of the transaction, not the error code. // kycUrl field of the transaction, not the error code.
const res: OperationAttemptResult = { const res: TaskRunResult = {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
{ {
@ -604,10 +595,7 @@ async function processPeerPullCreditKycRequired(
}; };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Pending,
result: undefined,
};
} else { } else {
throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`);
} }

View File

@ -62,8 +62,8 @@ import {
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkLogicInvariant } from "../util/invariants.js"; import { checkLogicInvariant } from "../util/invariants.js";
import { import {
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
TaskIdentifiers, TaskIdentifiers,
constructTaskIdentifier, constructTaskIdentifier,
runTaskWithErrorReporting, runTaskWithErrorReporting,
@ -89,12 +89,12 @@ async function handlePurseCreationConflict(
ws: InternalWalletState, ws: InternalWalletState,
peerPullInc: PeerPullPaymentIncomingRecord, peerPullInc: PeerPullPaymentIncomingRecord,
resp: HttpResponse, resp: HttpResponse,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const pursePub = peerPullInc.pursePub; const pursePub = peerPullInc.pursePub;
const errResp = await readTalerErrorResponse(resp); const errResp = await readTalerErrorResponse(resp);
if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
await failPeerPullDebitTransaction(ws, pursePub); await failPeerPullDebitTransaction(ws, pursePub);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
// FIXME: Properly parse! // FIXME: Properly parse!
@ -167,13 +167,13 @@ async function handlePurseCreationConflict(
} }
await tx.peerPullPaymentIncoming.put(myPpi); await tx.peerPullPaymentIncoming.put(myPpi);
}); });
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
async function processPeerPullDebitPendingDeposit( async function processPeerPullDebitPendingDeposit(
ws: InternalWalletState, ws: InternalWalletState,
peerPullInc: PeerPullPaymentIncomingRecord, peerPullInc: PeerPullPaymentIncomingRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId; const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId;
const pursePub = peerPullInc.pursePub; const pursePub = peerPullInc.pursePub;
@ -299,21 +299,18 @@ async function processPeerPullDebitPendingDeposit(
default: { default: {
const errResp = await readTalerErrorResponse(httpResp); const errResp = await readTalerErrorResponse(httpResp);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: errResp, errorDetail: errResp,
}; };
} }
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
async function processPeerPullDebitAbortingRefresh( async function processPeerPullDebitAbortingRefresh(
ws: InternalWalletState, ws: InternalWalletState,
peerPullInc: PeerPullPaymentIncomingRecord, peerPullInc: PeerPullPaymentIncomingRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId; const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId;
const abortRefreshGroupId = peerPullInc.abortRefreshGroupId; const abortRefreshGroupId = peerPullInc.abortRefreshGroupId;
checkLogicInvariant(!!abortRefreshGroupId); checkLogicInvariant(!!abortRefreshGroupId);
@ -357,13 +354,13 @@ async function processPeerPullDebitAbortingRefresh(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
// FIXME: Shouldn't this be finished in some cases?! // FIXME: Shouldn't this be finished in some cases?!
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
export async function processPeerPullDebit( export async function processPeerPullDebit(
ws: InternalWalletState, ws: InternalWalletState,
peerPullPaymentIncomingId: string, peerPullPaymentIncomingId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const peerPullInc = await ws.db const peerPullInc = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming]) .mktx((x) => [x.peerPullPaymentIncoming])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -379,10 +376,7 @@ export async function processPeerPullDebit(
case PeerPullDebitRecordStatus.AbortingRefresh: case PeerPullDebitRecordStatus.AbortingRefresh:
return await processPeerPullDebitAbortingRefresh(ws, peerPullInc); return await processPeerPullDebitAbortingRefresh(ws, peerPullInc);
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
export async function confirmPeerPullDebit( export async function confirmPeerPullDebit(

View File

@ -62,8 +62,8 @@ import {
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkDbInvariant } from "../util/invariants.js"; import { checkDbInvariant } from "../util/invariants.js";
import { import {
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
constructTaskIdentifier, constructTaskIdentifier,
runLongpollAsync, runLongpollAsync,
} from "./common.js"; } from "./common.js";
@ -233,7 +233,7 @@ async function longpollKycStatus(
exchangeUrl: string, exchangeUrl: string,
kycInfo: KycPendingInfo, kycInfo: KycPendingInfo,
userType: KycUserType, userType: KycUserType,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId, peerPushPaymentIncomingId,
@ -293,7 +293,7 @@ async function longpollKycStatus(
} }
}); });
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
@ -301,7 +301,7 @@ async function processPeerPushCreditKycRequired(
ws: InternalWalletState, ws: InternalWalletState,
peerInc: PeerPushPaymentIncomingRecord, peerInc: PeerPushPaymentIncomingRecord,
kycPending: WalletKycUuid, kycPending: WalletKycUuid,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId,
@ -326,10 +326,7 @@ async function processPeerPushCreditKycRequired(
kycStatusRes.status === HttpStatusCode.NoContent kycStatusRes.status === HttpStatusCode.NoContent
) { ) {
logger.warn("kyc requested, but already fulfilled"); logger.warn("kyc requested, but already fulfilled");
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} else if (kycStatusRes.status === HttpStatusCode.Accepted) { } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
const kycStatus = await kycStatusRes.json(); const kycStatus = await kycStatusRes.json();
logger.info(`kyc status: ${j2s(kycStatus)}`); logger.info(`kyc status: ${j2s(kycStatus)}`);
@ -342,7 +339,7 @@ async function processPeerPushCreditKycRequired(
if (!peerInc) { if (!peerInc) {
return { return {
transitionInfo: undefined, transitionInfo: undefined,
result: OperationAttemptResult.finishedEmpty(), result: TaskRunResult.finished(),
}; };
} }
const oldTxState = computePeerPushCreditTransactionState(peerInc); const oldTxState = computePeerPushCreditTransactionState(peerInc);
@ -356,8 +353,8 @@ async function processPeerPushCreditKycRequired(
await tx.peerPushPaymentIncoming.put(peerInc); await tx.peerPushPaymentIncoming.put(peerInc);
// We'll remove this eventually! New clients should rely on the // We'll remove this eventually! New clients should rely on the
// kycUrl field of the transaction, not the error code. // kycUrl field of the transaction, not the error code.
const res: OperationAttemptResult = { const res: TaskRunResult = {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
{ {
@ -381,7 +378,7 @@ async function handlePendingMerge(
ws: InternalWalletState, ws: InternalWalletState,
peerInc: PeerPushPaymentIncomingRecord, peerInc: PeerPushPaymentIncomingRecord,
contractTerms: PeerContractTerms, contractTerms: PeerContractTerms,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { peerPushPaymentIncomingId } = peerInc; const { peerPushPaymentIncomingId } = peerInc;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
@ -506,16 +503,13 @@ async function handlePendingMerge(
); );
notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition); notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition);
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
async function handlePendingWithdrawing( async function handlePendingWithdrawing(
ws: InternalWalletState, ws: InternalWalletState,
peerInc: PeerPushPaymentIncomingRecord, peerInc: PeerPushPaymentIncomingRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
if (!peerInc.withdrawalGroupId) { if (!peerInc.withdrawalGroupId) {
throw Error("invalid db state (withdrawing, but no withdrawal group ID"); throw Error("invalid db state (withdrawing, but no withdrawal group ID");
} }
@ -561,17 +555,17 @@ async function handlePendingWithdrawing(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
if (finished) { if (finished) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} else { } else {
// FIXME: Return indicator that we depend on the other operation! // FIXME: Return indicator that we depend on the other operation!
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
} }
export async function processPeerPushCredit( export async function processPeerPushCredit(
ws: InternalWalletState, ws: InternalWalletState,
peerPushPaymentIncomingId: string, peerPushPaymentIncomingId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
let peerInc: PeerPushPaymentIncomingRecord | undefined; let peerInc: PeerPushPaymentIncomingRecord | undefined;
let contractTerms: PeerContractTerms | undefined; let contractTerms: PeerContractTerms | undefined;
await ws.db await ws.db
@ -617,7 +611,7 @@ export async function processPeerPushCredit(
return handlePendingWithdrawing(ws, peerInc); return handlePendingWithdrawing(ws, peerInc);
default: default:
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
} }

View File

@ -61,8 +61,8 @@ import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkLogicInvariant } from "../util/invariants.js"; import { checkLogicInvariant } from "../util/invariants.js";
import { import {
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
constructTaskIdentifier, constructTaskIdentifier,
runLongpollAsync, runLongpollAsync,
spendCoins, spendCoins,
@ -110,12 +110,12 @@ async function handlePurseCreationConflict(
ws: InternalWalletState, ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord, peerPushInitiation: PeerPushPaymentInitiationRecord,
resp: HttpResponse, resp: HttpResponse,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const pursePub = peerPushInitiation.pursePub; const pursePub = peerPushInitiation.pursePub;
const errResp = await readTalerErrorResponse(resp); const errResp = await readTalerErrorResponse(resp);
if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
await failPeerPushDebitTransaction(ws, pursePub); await failPeerPushDebitTransaction(ws, pursePub);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
// FIXME: Properly parse! // FIXME: Properly parse!
@ -176,13 +176,13 @@ async function handlePurseCreationConflict(
} }
await tx.peerPushPaymentInitiations.put(myPpi); await tx.peerPushPaymentInitiations.put(myPpi);
}); });
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
async function processPeerPushDebitCreateReserve( async function processPeerPushDebitCreateReserve(
ws: InternalWalletState, ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord, peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info("processing peer-push-debit pending(create-reserve)"); logger.info("processing peer-push-debit pending(create-reserve)");
const pursePub = peerPushInitiation.pursePub; const pursePub = peerPushInitiation.pursePub;
const purseExpiration = peerPushInitiation.purseExpiration; const purseExpiration = peerPushInitiation.purseExpiration;
@ -264,7 +264,7 @@ async function processPeerPushDebitCreateReserve(
case HttpStatusCode.Forbidden: { case HttpStatusCode.Forbidden: {
// FIXME: Store this error! // FIXME: Store this error!
await failPeerPushDebitTransaction(ws, pursePub); await failPeerPushDebitTransaction(ws, pursePub);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
case HttpStatusCode.Conflict: { case HttpStatusCode.Conflict: {
// Handle double-spending // Handle double-spending
@ -273,7 +273,7 @@ async function processPeerPushDebitCreateReserve(
default: { default: {
const errResp = await readTalerErrorResponse(httpResp); const errResp = await readTalerErrorResponse(httpResp);
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: errResp, errorDetail: errResp,
}; };
} }
@ -289,13 +289,13 @@ async function processPeerPushDebitCreateReserve(
stTo: PeerPushPaymentInitiationStatus.PendingReady, stTo: PeerPushPaymentInitiationStatus.PendingReady,
}); });
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
async function processPeerPushDebitAbortingDeletePurse( async function processPeerPushDebitAbortingDeletePurse(
ws: InternalWalletState, ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord, peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { pursePub, pursePriv } = peerPushInitiation; const { pursePub, pursePriv } = peerPushInitiation;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit, tag: TransactionType.PeerPushDebit,
@ -364,7 +364,7 @@ async function processPeerPushDebitAbortingDeletePurse(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
interface SimpleTransition { interface SimpleTransition {
@ -406,7 +406,7 @@ async function transitionPeerPushDebitTransaction(
async function processPeerPushDebitAbortingRefresh( async function processPeerPushDebitAbortingRefresh(
ws: InternalWalletState, ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord, peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const pursePub = peerPushInitiation.pursePub; const pursePub = peerPushInitiation.pursePub;
const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId; const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId;
checkLogicInvariant(!!abortRefreshGroupId); checkLogicInvariant(!!abortRefreshGroupId);
@ -448,7 +448,7 @@ async function processPeerPushDebitAbortingRefresh(
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
// FIXME: Shouldn't this be finished in some cases?! // FIXME: Shouldn't this be finished in some cases?!
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
} }
/** /**
@ -457,7 +457,7 @@ async function processPeerPushDebitAbortingRefresh(
async function processPeerPushDebitReady( async function processPeerPushDebitReady(
ws: InternalWalletState, ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord, peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info("processing peer-push-debit pending(ready)"); logger.info("processing peer-push-debit pending(ready)");
const pursePub = peerPushInitiation.pursePub; const pursePub = peerPushInitiation.pursePub;
const retryTag = constructTaskIdentifier({ const retryTag = constructTaskIdentifier({
@ -520,14 +520,14 @@ async function processPeerPushDebitReady(
"returning early from peer-push-debit for long-polling in background", "returning early from peer-push-debit for long-polling in background",
); );
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
export async function processPeerPushDebit( export async function processPeerPushDebit(
ws: InternalWalletState, ws: InternalWalletState,
pursePub: string, pursePub: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const peerPushInitiation = await ws.db const peerPushInitiation = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations]) .mktx((x) => [x.peerPushPaymentInitiations])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -546,7 +546,7 @@ export async function processPeerPushDebit(
if (ws.activeLongpoll[retryTag]) { if (ws.activeLongpoll[retryTag]) {
logger.info("peer-push-debit task already in long-polling, returning!"); logger.info("peer-push-debit task already in long-polling, returning!");
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
@ -567,10 +567,7 @@ export async function processPeerPushDebit(
} }
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
/** /**

View File

@ -55,7 +55,7 @@ import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js"; import { GetReadWriteAccess } from "../util/query.js";
import { createRefreshGroup, processRefreshGroup } from "./refresh.js"; import { createRefreshGroup, processRefreshGroup } from "./refresh.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js"; import { internalCreateWithdrawalGroup } from "./withdraw.js";
import { OperationAttemptResult } from "./common.js"; import { TaskRunResult } from "./common.js";
const logger = new Logger("operations/recoup.ts"); const logger = new Logger("operations/recoup.ts");
@ -289,18 +289,18 @@ async function recoupRefreshCoin(
export async function processRecoupGroup( export async function processRecoupGroup(
ws: InternalWalletState, ws: InternalWalletState,
recoupGroupId: string, recoupGroupId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
let recoupGroup = await ws.db let recoupGroup = await ws.db
.mktx((x) => [x.recoupGroups]) .mktx((x) => [x.recoupGroups])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return tx.recoupGroups.get(recoupGroupId); return tx.recoupGroups.get(recoupGroupId);
}); });
if (!recoupGroup) { if (!recoupGroup) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
if (recoupGroup.timestampFinished) { if (recoupGroup.timestampFinished) {
logger.trace("recoup group finished"); logger.trace("recoup group finished");
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
const ps = recoupGroup.coinPubs.map(async (x, i) => { const ps = recoupGroup.coinPubs.map(async (x, i) => {
try { try {
@ -318,12 +318,12 @@ export async function processRecoupGroup(
return tx.recoupGroups.get(recoupGroupId); return tx.recoupGroups.get(recoupGroupId);
}); });
if (!recoupGroup) { if (!recoupGroup) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
for (const b of recoupGroup.recoupFinishedPerCoin) { for (const b of recoupGroup.recoupFinishedPerCoin) {
if (!b) { if (!b) {
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
} }
@ -408,7 +408,7 @@ export async function processRecoupGroup(
} }
await tx.recoupGroups.put(rg2); await tx.recoupGroups.put(rg2);
}); });
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
export async function createRecoupGroup( export async function createRecoupGroup(

View File

@ -89,8 +89,8 @@ import {
constructTaskIdentifier, constructTaskIdentifier,
makeCoinAvailable, makeCoinAvailable,
makeCoinsVisible, makeCoinsVisible,
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
} from "./common.js"; } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js"; import { updateExchangeFromUrl } from "./exchanges.js";
import { import {
@ -770,23 +770,17 @@ export async function processRefreshGroup(
ws: InternalWalletState, ws: InternalWalletState,
refreshGroupId: string, refreshGroupId: string,
options: Record<string, never> = {}, options: Record<string, never> = {},
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info(`processing refresh group ${refreshGroupId}`); logger.info(`processing refresh group ${refreshGroupId}`);
const refreshGroup = await ws.db const refreshGroup = await ws.db
.mktx((x) => [x.refreshGroups]) .mktx((x) => [x.refreshGroups])
.runReadOnly(async (tx) => tx.refreshGroups.get(refreshGroupId)); .runReadOnly(async (tx) => tx.refreshGroups.get(refreshGroupId));
if (!refreshGroup) { if (!refreshGroup) {
return { return TaskRunResult.finished()
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
if (refreshGroup.timestampFinished) { if (refreshGroup.timestampFinished) {
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
// Process refresh sessions of the group in parallel. // Process refresh sessions of the group in parallel.
logger.trace("processing refresh sessions for old coins"); logger.trace("processing refresh sessions for old coins");
@ -823,14 +817,11 @@ export async function processRefreshGroup(
logger.warn(`exception: ${e}`); logger.warn(`exception: ${e}`);
} }
if (inShutdown) { if (inShutdown) {
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Pending,
result: undefined,
};
} }
if (errors.length > 0) { if (errors.length > 0) {
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_REFRESH_GROUP_INCOMPLETE, TalerErrorCode.WALLET_REFRESH_GROUP_INCOMPLETE,
{ {
@ -841,10 +832,7 @@ export async function processRefreshGroup(
}; };
} }
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
async function processRefreshSession( async function processRefreshSession(
@ -1122,7 +1110,7 @@ function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime {
export async function autoRefresh( export async function autoRefresh(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`); logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
// We must make sure that the exchange is up-to-date so that // We must make sure that the exchange is up-to-date so that
@ -1204,7 +1192,7 @@ export async function autoRefresh(
AbsoluteTime.toPreciseTimestamp(minCheckThreshold); AbsoluteTime.toPreciseTimestamp(minCheckThreshold);
await tx.exchanges.put(exchange); await tx.exchanges.put(exchange);
}); });
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
export function computeRefreshTransactionState( export function computeRefreshTransactionState(

View File

@ -24,6 +24,7 @@ import {
Duration, Duration,
IntegrationTestV2Args, IntegrationTestV2Args,
Logger, Logger,
NotificationType,
stringToBytes, stringToBytes,
TestPayResult, TestPayResult,
WithdrawTestBalanceRequest, WithdrawTestBalanceRequest,
@ -64,6 +65,7 @@ import {
confirmPeerPushCredit, confirmPeerPushCredit,
} from "./pay-peer-push-credit.js"; } from "./pay-peer-push-credit.js";
import { initiatePeerPushDebit } from "./pay-peer-push-debit.js"; import { initiatePeerPushDebit } from "./pay-peer-push-debit.js";
import { OpenedPromise, openPromise } from "../index.js";
const logger = new Logger("operations/testing.ts"); const logger = new Logger("operations/testing.ts");
@ -445,6 +447,18 @@ export async function runIntegrationTest(
logger.trace("integration test: all done!"); logger.trace("integration test: all done!");
} }
async function waitUntilDone(ws: InternalWalletState): Promise<void> {
let p: OpenedPromise<void> | undefined = undefined;
ws.addNotificationListener((notif) => {
if (!p) {
return;
}
if (notif.type === NotificationType.TransactionStateTransition) {
p.resolve();
}
});
}
export async function runIntegrationTest2( export async function runIntegrationTest2(
ws: InternalWalletState, ws: InternalWalletState,
args: IntegrationTestV2Args, args: IntegrationTestV2Args,

View File

@ -62,8 +62,8 @@ import {
constructTaskIdentifier, constructTaskIdentifier,
makeCoinAvailable, makeCoinAvailable,
makeCoinsVisible, makeCoinsVisible,
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
} from "./common.js"; } from "./common.js";
import { updateExchangeFromUrl } from "./exchanges.js"; import { updateExchangeFromUrl } from "./exchanges.js";
import { import {
@ -241,17 +241,14 @@ export async function prepareTip(
export async function processTip( export async function processTip(
ws: InternalWalletState, ws: InternalWalletState,
walletTipId: string, walletTipId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const tipRecord = await ws.db const tipRecord = await ws.db
.mktx((x) => [x.tips]) .mktx((x) => [x.tips])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return tx.tips.get(walletTipId); return tx.tips.get(walletTipId);
}); });
if (!tipRecord) { if (!tipRecord) {
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
switch (tipRecord.status) { switch (tipRecord.status) {
@ -259,10 +256,7 @@ export async function processTip(
case TipRecordStatus.DialogAccept: case TipRecordStatus.DialogAccept:
case TipRecordStatus.Done: case TipRecordStatus.Done:
case TipRecordStatus.SuspendidPickup: case TipRecordStatus.SuspendidPickup:
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
@ -324,7 +318,7 @@ export async function processTip(
logger.trace(`got transient tip error`); logger.trace(`got transient tip error`);
// FIXME: wrap in another error code that indicates a transient error // FIXME: wrap in another error code that indicates a transient error
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
getHttpResponseErrorDetails(merchantResp), getHttpResponseErrorDetails(merchantResp),
@ -376,7 +370,7 @@ export async function processTip(
if (!isValid) { if (!isValid) {
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID, TalerErrorCode.WALLET_TIPPING_COIN_SIGNATURE_INVALID,
{}, {},
@ -430,10 +424,7 @@ export async function processTip(
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
ws.notify({ type: NotificationType.BalanceChange }); ws.notify({ type: NotificationType.BalanceChange });
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
export async function acceptTip( export async function acceptTip(

View File

@ -90,8 +90,8 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { import {
OperationAttemptResult, TaskRunResult,
OperationAttemptResultType, TaskRunResultType,
TaskIdentifiers, TaskIdentifiers,
constructTaskIdentifier, constructTaskIdentifier,
makeCoinAvailable, makeCoinAvailable,
@ -1326,7 +1326,7 @@ export interface WithdrawalGroupContext {
async function processWithdrawalGroupAbortingBank( async function processWithdrawalGroupAbortingBank(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroup: WithdrawalGroupRecord, withdrawalGroup: WithdrawalGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { withdrawalGroupId } = withdrawalGroup; const { withdrawalGroupId } = withdrawalGroup;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal, tag: TransactionType.Withdrawal,
@ -1363,10 +1363,7 @@ async function processWithdrawalGroupAbortingBank(
}; };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
/** /**
@ -1413,7 +1410,7 @@ async function transitionKycSatisfied(
async function processWithdrawalGroupPendingKyc( async function processWithdrawalGroupPendingKyc(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroup: WithdrawalGroupRecord, withdrawalGroup: WithdrawalGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const userType = "individual"; const userType = "individual";
const kycInfo = withdrawalGroup.kycPending; const kycInfo = withdrawalGroup.kycPending;
if (!kycInfo) { if (!kycInfo) {
@ -1456,13 +1453,13 @@ async function processWithdrawalGroupPendingKyc(
); );
} }
}); });
return OperationAttemptResult.longpoll(); return TaskRunResult.longpoll();
} }
async function processWithdrawalGroupPendingReady( async function processWithdrawalGroupPendingReady(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroup: WithdrawalGroupRecord, withdrawalGroup: WithdrawalGroupRecord,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
const { withdrawalGroupId } = withdrawalGroup; const { withdrawalGroupId } = withdrawalGroup;
const transactionId = constructTransactionIdentifier({ const transactionId = constructTransactionIdentifier({
tag: TransactionType.Withdrawal, tag: TransactionType.Withdrawal,
@ -1494,7 +1491,7 @@ async function processWithdrawalGroupPendingReady(
}; };
}); });
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
} }
const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms
@ -1608,7 +1605,7 @@ async function processWithdrawalGroupPendingReady(
if (numPlanchetErrors > 0) { if (numPlanchetErrors > 0) {
return { return {
type: OperationAttemptResultType.Error, type: TaskRunResultType.Error,
errorDetail: makeErrorDetail( errorDetail: makeErrorDetail(
TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE, TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE,
{ {
@ -1619,16 +1616,13 @@ async function processWithdrawalGroupPendingReady(
}; };
} }
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
} }
export async function processWithdrawalGroup( export async function processWithdrawalGroup(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroupId: string, withdrawalGroupId: string,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
logger.trace("processing withdrawal group", withdrawalGroupId); logger.trace("processing withdrawal group", withdrawalGroupId);
const withdrawalGroup = await ws.db const withdrawalGroup = await ws.db
.mktx((x) => [x.withdrawalGroups]) .mktx((x) => [x.withdrawalGroups])
@ -1646,7 +1640,7 @@ export async function processWithdrawalGroup(
if (ws.activeLongpoll[retryTag]) { if (ws.activeLongpoll[retryTag]) {
logger.info("withdrawal group already in long-polling, returning!"); logger.info("withdrawal group already in long-polling, returning!");
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
@ -1663,7 +1657,7 @@ export async function processWithdrawalGroup(
"returning early from withdrawal for long-polling in background", "returning early from withdrawal for long-polling in background",
); );
return { return {
type: OperationAttemptResultType.Longpoll, type: TaskRunResultType.Longpoll,
}; };
} }
case WithdrawalGroupStatus.PendingWaitConfirmBank: { case WithdrawalGroupStatus.PendingWaitConfirmBank: {
@ -1671,15 +1665,9 @@ export async function processWithdrawalGroup(
switch (res.status) { switch (res.status) {
case BankStatusResultCode.Aborted: case BankStatusResultCode.Aborted:
case BankStatusResultCode.Done: case BankStatusResultCode.Done:
return { return TaskRunResult.finished();
type: OperationAttemptResultType.Finished,
result: undefined,
};
case BankStatusResultCode.Waiting: { case BankStatusResultCode.Waiting: {
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Pending,
result: undefined,
};
} }
} }
break; break;
@ -1687,14 +1675,11 @@ export async function processWithdrawalGroup(
case WithdrawalGroupStatus.Finished: case WithdrawalGroupStatus.Finished:
case WithdrawalGroupStatus.FailedBankAborted: { case WithdrawalGroupStatus.FailedBankAborted: {
// FIXME // FIXME
return { return TaskRunResult.pending();
type: OperationAttemptResultType.Pending,
result: undefined,
};
} }
case WithdrawalGroupStatus.PendingAml: case WithdrawalGroupStatus.PendingAml:
// FIXME: Handle this case, withdrawal doesn't support AML yet. // FIXME: Handle this case, withdrawal doesn't support AML yet.
return OperationAttemptResult.pendingEmpty(); return TaskRunResult.pending();
case WithdrawalGroupStatus.PendingKyc: case WithdrawalGroupStatus.PendingKyc:
return processWithdrawalGroupPendingKyc(ws, withdrawalGroup); return processWithdrawalGroupPendingKyc(ws, withdrawalGroup);
case WithdrawalGroupStatus.PendingReady: case WithdrawalGroupStatus.PendingReady:
@ -1713,7 +1698,7 @@ export async function processWithdrawalGroup(
case WithdrawalGroupStatus.SuspendedRegisteringBank: case WithdrawalGroupStatus.SuspendedRegisteringBank:
case WithdrawalGroupStatus.SuspendedWaitConfirmBank: case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
// Nothing to do. // Nothing to do.
return OperationAttemptResult.finishedEmpty(); return TaskRunResult.finished();
default: default:
assertUnreachable(withdrawalGroup.status); assertUnreachable(withdrawalGroup.status);
} }

View File

@ -171,6 +171,7 @@ import {
import { setWalletDeviceId } from "./operations/backup/state.js"; import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalanceDetail, getBalances } from "./operations/balance.js"; import { getBalanceDetail, getBalances } from "./operations/balance.js";
import { import {
TaskRunResult,
getExchangeTosStatus, getExchangeTosStatus,
makeExchangeListItem, makeExchangeListItem,
runTaskWithErrorReporting, runTaskWithErrorReporting,
@ -287,7 +288,6 @@ import {
GetReadWriteAccess, GetReadWriteAccess,
} from "./util/query.js"; } from "./util/query.js";
import { import {
OperationAttemptResult,
TaskIdentifiers, TaskIdentifiers,
} from "./operations/common.js"; } from "./operations/common.js";
import { TimerAPI, TimerGroup } from "./util/timer.js"; import { TimerAPI, TimerGroup } from "./util/timer.js";
@ -320,7 +320,7 @@ const logger = new Logger("wallet.ts");
async function callOperationHandler( async function callOperationHandler(
ws: InternalWalletState, ws: InternalWalletState,
pending: PendingTaskInfo, pending: PendingTaskInfo,
): Promise<OperationAttemptResult> { ): Promise<TaskRunResult> {
switch (pending.type) { switch (pending.type) {
case PendingTaskType.ExchangeUpdate: case PendingTaskType.ExchangeUpdate:
return await updateExchangeFromUrlHandler(ws, pending.exchangeBaseUrl); return await updateExchangeFromUrlHandler(ws, pending.exchangeBaseUrl);

View File

@ -54,6 +54,11 @@ setPRNG(function (x: Uint8Array, n: number) {
const logger = new Logger("taler-wallet-embedded/index.ts"); const logger = new Logger("taler-wallet-embedded/index.ts");
/**
* Sends JSON to the host application, i.e. the process that
* runs the JavaScript interpreter (quickjs / qtart) to run
* the embedded wallet.
*/
function sendNativeMessage(ev: CoreApiMessageEnvelope): void { function sendNativeMessage(ev: CoreApiMessageEnvelope): void {
const m = JSON.stringify(ev); const m = JSON.stringify(ev);
qjsOs.postMessageToHost(m); qjsOs.postMessageToHost(m);
@ -183,21 +188,25 @@ export function installNativeWalletListener(): void {
const id = msg.id; const id = msg.id;
logger.info(`native listener: got request for ${operation} (${id})`); logger.info(`native listener: got request for ${operation} (${id})`);
let respMsg: CoreApiResponse; if (operation === "anastasisReduce") {
try { sendNativeMessage(respMsg);
respMsg = await handler.handleMessage(operation, id, msg.args ?? {}); } else {
} catch (e) { let respMsg: CoreApiResponse;
respMsg = { try {
type: "error", respMsg = await handler.handleMessage(operation, id, msg.args ?? {});
id, } catch (e) {
operation, respMsg = {
error: getErrorDetailFromException(e), type: "error",
}; id,
operation,
error: getErrorDetailFromException(e),
};
}
logger.info(
`native listener: sending back ${respMsg.type} message for operation ${operation} (${id})`,
);
sendNativeMessage(respMsg);
} }
logger.info(
`native listener: sending back ${respMsg.type} message for operation ${operation} (${id})`,
);
sendNativeMessage(respMsg);
}; };
qjsOs.setMessageFromHostHandler((m) => onMessage(m)); qjsOs.setMessageFromHostHandler((m) => onMessage(m));