wallet-core: fix retryTransaction, improve tx/op identifier parsing/construction
This commit is contained in:
parent
7bb81a008b
commit
3daa4dbb3f
@ -69,7 +69,7 @@ import {
|
|||||||
StoreDescriptor,
|
StoreDescriptor,
|
||||||
StoreWithIndexes,
|
StoreWithIndexes,
|
||||||
} from "./util/query.js";
|
} from "./util/query.js";
|
||||||
import { RetryInfo, RetryTags } from "./util/retries.js";
|
import { RetryInfo, TaskIdentifiers } from "./util/retries.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains the database schema of the Taler wallet together
|
* This file contains the database schema of the Taler wallet together
|
||||||
@ -1945,7 +1945,7 @@ export interface OperationRetryRecord {
|
|||||||
* Unique identifier for the operation. Typically of
|
* Unique identifier for the operation. Typically of
|
||||||
* the format `${opType}-${opUniqueKey}`
|
* the format `${opType}-${opUniqueKey}`
|
||||||
*
|
*
|
||||||
* @see {@link RetryTags}
|
* @see {@link TaskIdentifiers}
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
RetryTags,
|
TaskIdentifiers,
|
||||||
scheduleRetryInTx,
|
scheduleRetryInTx,
|
||||||
} from "../../util/retries.js";
|
} from "../../util/retries.js";
|
||||||
import { addAttentionRequest, removeAttentionRequest } from "../attention.js";
|
import { addAttentionRequest, removeAttentionRequest } from "../attention.js";
|
||||||
@ -379,7 +379,7 @@ async function runBackupCycleForProvider(
|
|||||||
logger.warn("backup provider not found anymore");
|
logger.warn("backup provider not found anymore");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forBackup(prov);
|
const opId = TaskIdentifiers.forBackup(prov);
|
||||||
await scheduleRetryInTx(ws, tx, opId);
|
await scheduleRetryInTx(ws, tx, opId);
|
||||||
prov.shouldRetryFreshProposal = true;
|
prov.shouldRetryFreshProposal = true;
|
||||||
prov.state = {
|
prov.state = {
|
||||||
@ -405,7 +405,7 @@ async function runBackupCycleForProvider(
|
|||||||
logger.warn("backup provider not found anymore");
|
logger.warn("backup provider not found anymore");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forBackup(prov);
|
const opId = TaskIdentifiers.forBackup(prov);
|
||||||
await scheduleRetryInTx(ws, tx, opId);
|
await scheduleRetryInTx(ws, tx, opId);
|
||||||
prov.currentPaymentProposalId = result.proposalId;
|
prov.currentPaymentProposalId = result.proposalId;
|
||||||
prov.shouldRetryFreshProposal = false;
|
prov.shouldRetryFreshProposal = false;
|
||||||
@ -479,7 +479,7 @@ async function runBackupCycleForProvider(
|
|||||||
prov.lastBackupHash = encodeCrock(hash(backupEnc));
|
prov.lastBackupHash = encodeCrock(hash(backupEnc));
|
||||||
// FIXME: Allocate error code for this situation?
|
// FIXME: Allocate error code for this situation?
|
||||||
// FIXME: Add operation retry record!
|
// FIXME: Add operation retry record!
|
||||||
const opId = RetryTags.forBackup(prov);
|
const opId = TaskIdentifiers.forBackup(prov);
|
||||||
await scheduleRetryInTx(ws, tx, opId);
|
await scheduleRetryInTx(ws, tx, opId);
|
||||||
prov.state = {
|
prov.state = {
|
||||||
tag: BackupProviderStateTag.Retrying,
|
tag: BackupProviderStateTag.Retrying,
|
||||||
@ -920,7 +920,7 @@ export async function getBackupInfo(
|
|||||||
.mktx((x) => [x.backupProviders, x.operationRetries])
|
.mktx((x) => [x.backupProviders, x.operationRetries])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return await tx.backupProviders.iter().mapAsync(async (bp) => {
|
return await tx.backupProviders.iter().mapAsync(async (bp) => {
|
||||||
const opId = RetryTags.forBackup(bp);
|
const opId = TaskIdentifiers.forBackup(bp);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
return {
|
return {
|
||||||
provider: bp,
|
provider: bp,
|
||||||
|
@ -218,6 +218,23 @@ export async function storeOperationError(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resetOperationTimeout(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
pendingTaskId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await ws.db
|
||||||
|
.mktx((x) => [x.operationRetries])
|
||||||
|
.runReadWrite(async (tx) => {
|
||||||
|
let retryRecord = await tx.operationRetries.get(pendingTaskId);
|
||||||
|
if (retryRecord) {
|
||||||
|
// Note that we don't reset the lastError, it should still be visible
|
||||||
|
// while the retry runs.
|
||||||
|
retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
|
||||||
|
await tx.operationRetries.put(retryRecord);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function storeOperationPending(
|
export async function storeOperationPending(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
pendingTaskId: string,
|
pendingTaskId: string,
|
||||||
|
@ -122,7 +122,6 @@ export async function processDepositGroup(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
depositGroupId: string,
|
depositGroupId: string,
|
||||||
options: {
|
options: {
|
||||||
forceNow?: boolean;
|
|
||||||
cancellationToken?: CancellationToken;
|
cancellationToken?: CancellationToken;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
|
@ -73,7 +73,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
RetryTags,
|
TaskIdentifiers,
|
||||||
unwrapOperationHandlerResultOrThrow,
|
unwrapOperationHandlerResultOrThrow,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js";
|
import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js";
|
||||||
@ -552,7 +552,7 @@ export async function updateExchangeFromUrl(
|
|||||||
return unwrapOperationHandlerResultOrThrow(
|
return unwrapOperationHandlerResultOrThrow(
|
||||||
await runOperationWithErrorReporting(
|
await runOperationWithErrorReporting(
|
||||||
ws,
|
ws,
|
||||||
RetryTags.forExchangeUpdateFromUrl(canonUrl),
|
TaskIdentifiers.forExchangeUpdateFromUrl(canonUrl),
|
||||||
() => updateExchangeFromUrlHandler(ws, canonUrl, options),
|
() => updateExchangeFromUrlHandler(ws, canonUrl, options),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -95,7 +95,7 @@ import {
|
|||||||
TalerError,
|
TalerError,
|
||||||
TalerProtocolViolationError,
|
TalerProtocolViolationError,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { GetReadWriteAccess } from "../index.js";
|
import { GetReadWriteAccess, PendingTaskType } from "../index.js";
|
||||||
import {
|
import {
|
||||||
EXCHANGE_COINS_LOCK,
|
EXCHANGE_COINS_LOCK,
|
||||||
InternalWalletState,
|
InternalWalletState,
|
||||||
@ -119,8 +119,9 @@ import {
|
|||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
RetryInfo,
|
RetryInfo,
|
||||||
RetryTags,
|
TaskIdentifiers,
|
||||||
scheduleRetry,
|
scheduleRetry,
|
||||||
|
constructTaskIdentifier,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
makeTransactionId,
|
makeTransactionId,
|
||||||
@ -360,7 +361,7 @@ export async function processDownloadProposal(
|
|||||||
requestBody.token = proposal.claimToken;
|
requestBody.token = proposal.claimToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
const opId = RetryTags.forPay(proposal);
|
const opId = TaskIdentifiers.forPay(proposal);
|
||||||
const retryRecord = await ws.db
|
const retryRecord = await ws.db
|
||||||
.mktx((x) => [x.operationRetries])
|
.mktx((x) => [x.operationRetries])
|
||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
@ -1598,8 +1599,11 @@ export async function runPayForConfirmPay(
|
|||||||
proposalId: string,
|
proposalId: string,
|
||||||
): Promise<ConfirmPayResult> {
|
): Promise<ConfirmPayResult> {
|
||||||
logger.trace("processing proposal for confirmPay");
|
logger.trace("processing proposal for confirmPay");
|
||||||
const opId = RetryTags.byPaymentProposalId(proposalId);
|
const taskId = constructTaskIdentifier({
|
||||||
const res = await runOperationWithErrorReporting(ws, opId, async () => {
|
tag: PendingTaskType.Purchase,
|
||||||
|
proposalId,
|
||||||
|
});
|
||||||
|
const res = await runOperationWithErrorReporting(ws, taskId, async () => {
|
||||||
return await processPurchasePay(ws, proposalId, { forceNow: true });
|
return await processPurchasePay(ws, proposalId, { forceNow: true });
|
||||||
});
|
});
|
||||||
logger.trace(`processPurchasePay response type ${res.type}`);
|
logger.trace(`processPurchasePay response type ${res.type}`);
|
||||||
@ -1624,9 +1628,7 @@ export async function runPayForConfirmPay(
|
|||||||
// 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])
|
||||||
.runReadOnly(async (tx) =>
|
.runReadOnly(async (tx) => tx.operationRetries.get(taskId));
|
||||||
tx.operationRetries.get(RetryTags.byPaymentProposalId(proposalId)),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
type: ConfirmPayResultType.Pending,
|
type: ConfirmPayResultType.Pending,
|
||||||
lastError: opRetry?.lastError,
|
lastError: opRetry?.lastError,
|
||||||
@ -1792,9 +1794,7 @@ export async function confirmPay(
|
|||||||
export async function processPurchase(
|
export async function processPurchase(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
proposalId: string,
|
proposalId: string,
|
||||||
options: {
|
options: Record<any, never> = {},
|
||||||
forceNow?: boolean;
|
|
||||||
} = {},
|
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
const purchase = await ws.db
|
const purchase = await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases])
|
||||||
@ -1843,9 +1843,7 @@ export async function processPurchase(
|
|||||||
export async function processPurchasePay(
|
export async function processPurchasePay(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
proposalId: string,
|
proposalId: string,
|
||||||
options: {
|
options: unknown = {},
|
||||||
forceNow?: boolean;
|
|
||||||
} = {},
|
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
const purchase = await ws.db
|
const purchase = await ws.db
|
||||||
.mktx((x) => [x.purchases])
|
.mktx((x) => [x.purchases])
|
||||||
@ -1935,7 +1933,7 @@ export async function processPurchasePay(
|
|||||||
handleInsufficientFunds(ws, proposalId, err).catch(async (e) => {
|
handleInsufficientFunds(ws, proposalId, err).catch(async (e) => {
|
||||||
console.log("handling insufficient funds failed");
|
console.log("handling insufficient funds failed");
|
||||||
|
|
||||||
await scheduleRetry(ws, RetryTags.forPay(purchase), {
|
await scheduleRetry(ws, TaskIdentifiers.forPay(purchase), {
|
||||||
code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
|
code: TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
|
||||||
when: AbsoluteTime.now(),
|
when: AbsoluteTime.now(),
|
||||||
message: "unexpected exception",
|
message: "unexpected exception",
|
||||||
@ -2830,7 +2828,10 @@ export async function abortPay(
|
|||||||
proposalId: string,
|
proposalId: string,
|
||||||
cancelImmediately?: boolean,
|
cancelImmediately?: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const opId = RetryTags.byPaymentProposalId(proposalId);
|
const opId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Purchase,
|
||||||
|
proposalId,
|
||||||
|
});
|
||||||
await ws.db
|
await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.purchases,
|
x.purchases,
|
||||||
|
@ -87,15 +87,17 @@ import { TalerError } from "@gnu-taler/taler-util";
|
|||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import {
|
import {
|
||||||
makeTransactionId,
|
makeTransactionId,
|
||||||
|
resetOperationTimeout,
|
||||||
runOperationWithErrorReporting,
|
runOperationWithErrorReporting,
|
||||||
spendCoins,
|
spendCoins,
|
||||||
} from "../operations/common.js";
|
} from "../operations/common.js";
|
||||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||||
import { checkDbInvariant } from "../util/invariants.js";
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
import {
|
import {
|
||||||
|
constructTaskIdentifier,
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
RetryTags,
|
TaskIdentifiers,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
|
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
|
||||||
import { updateExchangeFromUrl } from "./exchanges.js";
|
import { updateExchangeFromUrl } from "./exchanges.js";
|
||||||
@ -103,7 +105,10 @@ import { getTotalRefreshCost } from "./refresh.js";
|
|||||||
import {
|
import {
|
||||||
getExchangeWithdrawalInfo,
|
getExchangeWithdrawalInfo,
|
||||||
internalCreateWithdrawalGroup,
|
internalCreateWithdrawalGroup,
|
||||||
|
processWithdrawalGroup,
|
||||||
} from "./withdraw.js";
|
} from "./withdraw.js";
|
||||||
|
import { PendingTaskType } from "../pending-types.js";
|
||||||
|
import { stopLongpolling } from "./transactions.js";
|
||||||
|
|
||||||
const logger = new Logger("operations/peer-to-peer.ts");
|
const logger = new Logger("operations/peer-to-peer.ts");
|
||||||
|
|
||||||
@ -590,13 +595,14 @@ export async function initiatePeerPushPayment(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await runOperationWithErrorReporting(
|
const taskId = constructTaskIdentifier({
|
||||||
ws,
|
tag: PendingTaskType.PeerPushInitiation,
|
||||||
RetryTags.byPeerPushPaymentInitiationPursePub(pursePair.pub),
|
pursePub: pursePair.pub,
|
||||||
async () => {
|
});
|
||||||
return await processPeerPushInitiation(ws, pursePair.pub);
|
|
||||||
},
|
await runOperationWithErrorReporting(ws, taskId, async () => {
|
||||||
);
|
return await processPeerPushInitiation(ws, pursePair.pub);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contractPriv: contractKeyPair.priv,
|
contractPriv: contractKeyPair.priv,
|
||||||
@ -951,7 +957,7 @@ export async function confirmPeerPushPayment(
|
|||||||
|
|
||||||
await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
|
await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
|
||||||
|
|
||||||
const retryTag = RetryTags.forPeerPushCredit(peerInc);
|
const retryTag = TaskIdentifiers.forPeerPushCredit(peerInc);
|
||||||
|
|
||||||
await runOperationWithErrorReporting(ws, retryTag, () =>
|
await runOperationWithErrorReporting(ws, retryTag, () =>
|
||||||
processPeerPushCredit(ws, req.peerPushPaymentIncomingId),
|
processPeerPushCredit(ws, req.peerPushPaymentIncomingId),
|
||||||
@ -1113,7 +1119,7 @@ export async function acceptIncomingPeerPullPayment(
|
|||||||
|
|
||||||
await runOperationWithErrorReporting(
|
await runOperationWithErrorReporting(
|
||||||
ws,
|
ws,
|
||||||
RetryTags.forPeerPullPaymentDebit(ppi),
|
TaskIdentifiers.forPeerPullPaymentDebit(ppi),
|
||||||
async () => {
|
async () => {
|
||||||
return processPeerPullDebit(ws, ppi.peerPullPaymentIncomingId);
|
return processPeerPullDebit(ws, ppi.peerPullPaymentIncomingId);
|
||||||
},
|
},
|
||||||
@ -1263,7 +1269,23 @@ export async function processPeerPullCredit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pullIni.status === OperationStatus.Finished) {
|
if (pullIni.status === OperationStatus.Finished) {
|
||||||
logger.warn("peer pull payment initiation is already finished");
|
logger.warn(
|
||||||
|
"peer pull payment initiation is already finished, retrying withdrawal",
|
||||||
|
);
|
||||||
|
|
||||||
|
const withdrawalGroupId = pullIni.withdrawalGroupId;
|
||||||
|
|
||||||
|
if (withdrawalGroupId) {
|
||||||
|
const taskId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Withdraw,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processWithdrawalGroup(ws, withdrawalGroupId),
|
||||||
|
);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: OperationAttemptResultType.Finished,
|
type: OperationAttemptResultType.Finished,
|
||||||
result: undefined,
|
result: undefined,
|
||||||
@ -1514,19 +1536,19 @@ export async function initiatePeerPullPayment(
|
|||||||
// whether purse creation has failed, or does the client/
|
// whether purse creation has failed, or does the client/
|
||||||
// check this asynchronously from the transaction status?
|
// check this asynchronously from the transaction status?
|
||||||
|
|
||||||
await runOperationWithErrorReporting(
|
const taskId = constructTaskIdentifier({
|
||||||
ws,
|
tag: PendingTaskType.PeerPullInitiation,
|
||||||
RetryTags.byPeerPullPaymentInitiationPursePub(pursePair.pub),
|
pursePub: pursePair.pub,
|
||||||
async () => {
|
});
|
||||||
return processPeerPullCredit(ws, pursePair.pub);
|
|
||||||
},
|
await runOperationWithErrorReporting(ws, taskId, async () => {
|
||||||
);
|
return processPeerPullCredit(ws, pursePair.pub);
|
||||||
|
});
|
||||||
|
|
||||||
// FIXME: Why do we create this only here?
|
// FIXME: Why do we create this only here?
|
||||||
// What if the previous operation didn't succeed?
|
// What if the previous operation didn't succeed?
|
||||||
|
// We actually should create it once we know the
|
||||||
// FIXME: Use a pre-computed withdrawal group ID
|
// money arrived (via long-polling).
|
||||||
// so we don't create it multiple times.
|
|
||||||
|
|
||||||
await internalCreateWithdrawalGroup(ws, {
|
await internalCreateWithdrawalGroup(ws, {
|
||||||
amount: instructedAmount,
|
amount: instructedAmount,
|
||||||
|
@ -39,7 +39,7 @@ import {
|
|||||||
import { AbsoluteTime } from "@gnu-taler/taler-util";
|
import { AbsoluteTime } from "@gnu-taler/taler-util";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { GetReadOnlyAccess } from "../util/query.js";
|
import { GetReadOnlyAccess } from "../util/query.js";
|
||||||
import { RetryTags } from "../util/retries.js";
|
import { TaskIdentifiers } from "../util/retries.js";
|
||||||
import { GlobalIDB } from "@gnu-taler/idb-bridge";
|
import { GlobalIDB } from "@gnu-taler/idb-bridge";
|
||||||
|
|
||||||
function getPendingCommon(
|
function getPendingCommon(
|
||||||
@ -74,7 +74,7 @@ async function gatherExchangePending(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// FIXME: We should do a range query here based on the update time.
|
// FIXME: We should do a range query here based on the update time.
|
||||||
await tx.exchanges.iter().forEachAsync(async (exch) => {
|
await tx.exchanges.iter().forEachAsync(async (exch) => {
|
||||||
const opTag = RetryTags.forExchangeUpdate(exch);
|
const opTag = TaskIdentifiers.forExchangeUpdate(exch);
|
||||||
let opr = await tx.operationRetries.get(opTag);
|
let opr = await tx.operationRetries.get(opTag);
|
||||||
const timestampDue =
|
const timestampDue =
|
||||||
opr?.retryInfo.nextRetry ?? AbsoluteTime.fromTimestamp(exch.nextUpdate);
|
opr?.retryInfo.nextRetry ?? AbsoluteTime.fromTimestamp(exch.nextUpdate);
|
||||||
@ -120,7 +120,7 @@ async function gatherRefreshPending(
|
|||||||
if (r.timestampFinished) {
|
if (r.timestampFinished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forRefresh(r);
|
const opId = TaskIdentifiers.forRefresh(r);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
|
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
@ -158,7 +158,7 @@ async function gatherWithdrawalPending(
|
|||||||
if (wsr.timestampFinish) {
|
if (wsr.timestampFinish) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opTag = RetryTags.forWithdrawal(wsr);
|
const opTag = TaskIdentifiers.forWithdrawal(wsr);
|
||||||
let opr = await tx.operationRetries.get(opTag);
|
let opr = await tx.operationRetries.get(opTag);
|
||||||
const now = AbsoluteTime.now();
|
const now = AbsoluteTime.now();
|
||||||
if (!opr) {
|
if (!opr) {
|
||||||
@ -208,7 +208,7 @@ async function gatherDepositPending(
|
|||||||
deposited = false;
|
deposited = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forDeposit(dg);
|
const opId = TaskIdentifiers.forDeposit(dg);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
@ -239,7 +239,7 @@ async function gatherTipPending(
|
|||||||
if (tip.pickedUpTimestamp) {
|
if (tip.pickedUpTimestamp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forTipPickup(tip);
|
const opId = TaskIdentifiers.forTipPickup(tip);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
if (tip.acceptedTimestamp) {
|
if (tip.acceptedTimestamp) {
|
||||||
@ -272,7 +272,7 @@ async function gatherPurchasePending(
|
|||||||
await tx.purchases.indexes.byStatus
|
await tx.purchases.indexes.byStatus
|
||||||
.iter(keyRange)
|
.iter(keyRange)
|
||||||
.forEachAsync(async (pr) => {
|
.forEachAsync(async (pr) => {
|
||||||
const opId = RetryTags.forPay(pr);
|
const opId = TaskIdentifiers.forPay(pr);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue =
|
const timestampDue =
|
||||||
retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
@ -301,7 +301,7 @@ async function gatherRecoupPending(
|
|||||||
if (rg.timestampFinished) {
|
if (rg.timestampFinished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forRecoup(rg);
|
const opId = TaskIdentifiers.forRecoup(rg);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
@ -325,7 +325,7 @@ async function gatherBackupPending(
|
|||||||
resp: PendingOperationsResponse,
|
resp: PendingOperationsResponse,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await tx.backupProviders.iter().forEachAsync(async (bp) => {
|
await tx.backupProviders.iter().forEachAsync(async (bp) => {
|
||||||
const opId = RetryTags.forBackup(bp);
|
const opId = TaskIdentifiers.forBackup(bp);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
if (bp.state.tag === BackupProviderStateTag.Ready) {
|
if (bp.state.tag === BackupProviderStateTag.Ready) {
|
||||||
const timestampDue = AbsoluteTime.fromTimestamp(
|
const timestampDue = AbsoluteTime.fromTimestamp(
|
||||||
@ -366,7 +366,7 @@ async function gatherPeerPullInitiationPending(
|
|||||||
if (pi.status === OperationStatus.Finished) {
|
if (pi.status === OperationStatus.Finished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forPeerPullPaymentInitiation(pi);
|
const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
@ -392,7 +392,7 @@ async function gatherPeerPullDebitPending(
|
|||||||
if (pi.status === PeerPullPaymentIncomingStatus.Paid) {
|
if (pi.status === PeerPullPaymentIncomingStatus.Paid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forPeerPullPaymentDebit(pi);
|
const opId = TaskIdentifiers.forPeerPullPaymentDebit(pi);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
@ -418,7 +418,7 @@ async function gatherPeerPushInitiationPending(
|
|||||||
if (pi.status === PeerPushPaymentInitiationStatus.PurseCreated) {
|
if (pi.status === PeerPushPaymentInitiationStatus.PurseCreated) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forPeerPushPaymentInitiation(pi);
|
const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
@ -447,7 +447,7 @@ async function gatherPeerPushCreditPending(
|
|||||||
case PeerPushPaymentIncomingStatus.WithdrawalCreated:
|
case PeerPushPaymentIncomingStatus.WithdrawalCreated:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forPeerPushCredit(pi);
|
const opId = TaskIdentifiers.forPeerPushCredit(pi);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
|
@ -734,9 +734,7 @@ async function refreshReveal(
|
|||||||
export async function processRefreshGroup(
|
export async function processRefreshGroup(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
refreshGroupId: string,
|
refreshGroupId: string,
|
||||||
options: {
|
options: Record<string, never> = {},
|
||||||
forceNow?: boolean;
|
|
||||||
} = {},
|
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
logger.info(`processing refresh group ${refreshGroupId}`);
|
logger.info(`processing refresh group ${refreshGroupId}`);
|
||||||
|
|
||||||
|
@ -164,9 +164,7 @@ export async function prepareTip(
|
|||||||
export async function processTip(
|
export async function processTip(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
walletTipId: string,
|
walletTipId: string,
|
||||||
options: {
|
options: Record<string, never> = {},
|
||||||
forceNow?: boolean;
|
|
||||||
} = {},
|
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
const tipRecord = await ws.db
|
const tipRecord = await ws.db
|
||||||
.mktx((x) => [x.tips])
|
.mktx((x) => [x.tips])
|
||||||
|
@ -63,12 +63,15 @@ import {
|
|||||||
PeerPullPaymentInitiationRecord,
|
PeerPullPaymentInitiationRecord,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
|
import { PendingTaskType } from "../pending-types.js";
|
||||||
import { checkDbInvariant } from "../util/invariants.js";
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
import { RetryTags } from "../util/retries.js";
|
import { constructTaskIdentifier, TaskIdentifiers } from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
makeTombstoneId,
|
makeTombstoneId,
|
||||||
makeTransactionId,
|
makeTransactionId,
|
||||||
parseId,
|
parseId,
|
||||||
|
resetOperationTimeout,
|
||||||
|
runOperationWithErrorReporting,
|
||||||
TombstoneTag,
|
TombstoneTag,
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import { processDepositGroup } from "./deposits.js";
|
import { processDepositGroup } from "./deposits.js";
|
||||||
@ -79,6 +82,7 @@ import {
|
|||||||
extractContractData,
|
extractContractData,
|
||||||
processPurchasePay,
|
processPurchasePay,
|
||||||
} from "./pay-merchant.js";
|
} from "./pay-merchant.js";
|
||||||
|
import { processPeerPullCredit } from "./pay-peer.js";
|
||||||
import { processRefreshGroup } from "./refresh.js";
|
import { processRefreshGroup } from "./refresh.js";
|
||||||
import { processTip } from "./tip.js";
|
import { processTip } from "./tip.js";
|
||||||
import {
|
import {
|
||||||
@ -152,7 +156,7 @@ export async function getTransactionById(
|
|||||||
|
|
||||||
if (!withdrawalGroupRecord) throw Error("not found");
|
if (!withdrawalGroupRecord) throw Error("not found");
|
||||||
|
|
||||||
const opId = RetryTags.forWithdrawal(withdrawalGroupRecord);
|
const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
|
||||||
const ort = await tx.operationRetries.get(opId);
|
const ort = await tx.operationRetries.get(opId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -215,7 +219,7 @@ export async function getTransactionById(
|
|||||||
Amounts.zeroOfAmount(contractData.amount),
|
Amounts.zeroOfAmount(contractData.amount),
|
||||||
);
|
);
|
||||||
|
|
||||||
const payOpId = RetryTags.forPay(purchase);
|
const payOpId = TaskIdentifiers.forPay(purchase);
|
||||||
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
||||||
|
|
||||||
return buildTransactionForPurchase(
|
return buildTransactionForPurchase(
|
||||||
@ -237,7 +241,7 @@ export async function getTransactionById(
|
|||||||
if (!tipRecord) throw Error("not found");
|
if (!tipRecord) throw Error("not found");
|
||||||
|
|
||||||
const retries = await tx.operationRetries.get(
|
const retries = await tx.operationRetries.get(
|
||||||
RetryTags.forTipPickup(tipRecord),
|
TaskIdentifiers.forTipPickup(tipRecord),
|
||||||
);
|
);
|
||||||
return buildTransactionForTip(tipRecord, retries);
|
return buildTransactionForTip(tipRecord, retries);
|
||||||
});
|
});
|
||||||
@ -250,7 +254,7 @@ export async function getTransactionById(
|
|||||||
if (!depositRecord) throw Error("not found");
|
if (!depositRecord) throw Error("not found");
|
||||||
|
|
||||||
const retries = await tx.operationRetries.get(
|
const retries = await tx.operationRetries.get(
|
||||||
RetryTags.forDeposit(depositRecord),
|
TaskIdentifiers.forDeposit(depositRecord),
|
||||||
);
|
);
|
||||||
return buildTransactionForDeposit(depositRecord, retries);
|
return buildTransactionForDeposit(depositRecord, retries);
|
||||||
});
|
});
|
||||||
@ -359,11 +363,11 @@ export async function getTransactionById(
|
|||||||
if (pushInc.withdrawalGroupId) {
|
if (pushInc.withdrawalGroupId) {
|
||||||
wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
|
wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
|
||||||
if (wg) {
|
if (wg) {
|
||||||
const withdrawalOpId = RetryTags.forWithdrawal(wg);
|
const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
|
||||||
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pushIncOpId = RetryTags.forPeerPushCredit(pushInc);
|
const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc);
|
||||||
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
||||||
|
|
||||||
return buildTransactionForPeerPushCredit(
|
return buildTransactionForPeerPushCredit(
|
||||||
@ -394,11 +398,12 @@ export async function getTransactionById(
|
|||||||
if (pushInc.withdrawalGroupId) {
|
if (pushInc.withdrawalGroupId) {
|
||||||
wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
|
wg = await tx.withdrawalGroups.get(pushInc.withdrawalGroupId);
|
||||||
if (wg) {
|
if (wg) {
|
||||||
const withdrawalOpId = RetryTags.forWithdrawal(wg);
|
const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
|
||||||
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pushIncOpId = RetryTags.forPeerPullPaymentInitiation(pushInc);
|
const pushIncOpId =
|
||||||
|
TaskIdentifiers.forPeerPullPaymentInitiation(pushInc);
|
||||||
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
||||||
|
|
||||||
return buildTransactionForPeerPullCredit(
|
return buildTransactionForPeerPullCredit(
|
||||||
@ -1109,11 +1114,11 @@ export async function getTransactions(
|
|||||||
if (pi.withdrawalGroupId) {
|
if (pi.withdrawalGroupId) {
|
||||||
wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
|
wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
|
||||||
if (wg) {
|
if (wg) {
|
||||||
const withdrawalOpId = RetryTags.forWithdrawal(wg);
|
const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
|
||||||
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pushIncOpId = RetryTags.forPeerPushCredit(pi);
|
const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi);
|
||||||
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
||||||
|
|
||||||
checkDbInvariant(!!ct);
|
checkDbInvariant(!!ct);
|
||||||
@ -1142,11 +1147,11 @@ export async function getTransactions(
|
|||||||
if (pi.withdrawalGroupId) {
|
if (pi.withdrawalGroupId) {
|
||||||
wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
|
wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId);
|
||||||
if (wg) {
|
if (wg) {
|
||||||
const withdrawalOpId = RetryTags.forWithdrawal(wg);
|
const withdrawalOpId = TaskIdentifiers.forWithdrawal(wg);
|
||||||
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
wgOrt = await tx.operationRetries.get(withdrawalOpId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pushIncOpId = RetryTags.forPeerPullPaymentInitiation(pi);
|
const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
|
||||||
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
|
||||||
|
|
||||||
checkDbInvariant(!!ct);
|
checkDbInvariant(!!ct);
|
||||||
@ -1166,7 +1171,7 @@ export async function getTransactions(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let required = false;
|
let required = false;
|
||||||
const opId = RetryTags.forRefresh(rg);
|
const opId = TaskIdentifiers.forRefresh(rg);
|
||||||
if (transactionsRequest?.includeRefreshes) {
|
if (transactionsRequest?.includeRefreshes) {
|
||||||
required = true;
|
required = true;
|
||||||
} else if (rg.operationStatus !== RefreshOperationStatus.Finished) {
|
} else if (rg.operationStatus !== RefreshOperationStatus.Finished) {
|
||||||
@ -1195,7 +1200,7 @@ export async function getTransactions(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const opId = RetryTags.forWithdrawal(wsr);
|
const opId = TaskIdentifiers.forWithdrawal(wsr);
|
||||||
const ort = await tx.operationRetries.get(opId);
|
const ort = await tx.operationRetries.get(opId);
|
||||||
|
|
||||||
switch (wsr.wgInfo.withdrawalType) {
|
switch (wsr.wgInfo.withdrawalType) {
|
||||||
@ -1238,7 +1243,7 @@ export async function getTransactions(
|
|||||||
if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
|
if (shouldSkipCurrency(transactionsRequest, amount.currency)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forDeposit(dg);
|
const opId = TaskIdentifiers.forDeposit(dg);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
|
|
||||||
transactions.push(buildTransactionForDeposit(dg, retryRecord));
|
transactions.push(buildTransactionForDeposit(dg, retryRecord));
|
||||||
@ -1309,7 +1314,7 @@ export async function getTransactions(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const payOpId = RetryTags.forPay(purchase);
|
const payOpId = TaskIdentifiers.forPay(purchase);
|
||||||
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
const payRetryRecord = await tx.operationRetries.get(payOpId);
|
||||||
transactions.push(
|
transactions.push(
|
||||||
await buildTransactionForPurchase(
|
await buildTransactionForPurchase(
|
||||||
@ -1333,7 +1338,7 @@ export async function getTransactions(
|
|||||||
if (!tipRecord.acceptedTimestamp) {
|
if (!tipRecord.acceptedTimestamp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opId = RetryTags.forTipPickup(tipRecord);
|
const opId = TaskIdentifiers.forTipPickup(tipRecord);
|
||||||
const retryRecord = await tx.operationRetries.get(opId);
|
const retryRecord = await tx.operationRetries.get(opId);
|
||||||
transactions.push(buildTransactionForTip(tipRecord, retryRecord));
|
transactions.push(buildTransactionForTip(tipRecord, retryRecord));
|
||||||
});
|
});
|
||||||
@ -1359,6 +1364,77 @@ export async function getTransactions(
|
|||||||
return { transactions: [...txNotPending, ...txPending] };
|
return { transactions: [...txNotPending, ...txPending] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ParsedTransactionIdentifier =
|
||||||
|
| { tag: TransactionType.Deposit; depositGroupId: string }
|
||||||
|
| { tag: TransactionType.Payment; proposalId: string }
|
||||||
|
| { tag: TransactionType.PeerPullDebit; peerPullPaymentIncomingId: string }
|
||||||
|
| { tag: TransactionType.PeerPullCredit; pursePub: string }
|
||||||
|
| { tag: TransactionType.PeerPushCredit; peerPushPaymentIncomingId: string }
|
||||||
|
| { tag: TransactionType.PeerPushDebit; pursePub: string }
|
||||||
|
| { tag: TransactionType.Refresh; refreshGroupId: string }
|
||||||
|
| { tag: TransactionType.Refund; proposalId: string; executionTime: string }
|
||||||
|
| { tag: TransactionType.Tip; walletTipId: string }
|
||||||
|
| { tag: TransactionType.Withdrawal; withdrawalGroupId: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a transaction identifier string into a typed, structured representation.
|
||||||
|
*/
|
||||||
|
export function parseTransactionIdentifier(
|
||||||
|
transactionId: string,
|
||||||
|
): ParsedTransactionIdentifier | undefined {
|
||||||
|
const { type, args: rest } = parseId("any", transactionId);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TransactionType.Deposit:
|
||||||
|
return { tag: TransactionType.Deposit, depositGroupId: rest[0] };
|
||||||
|
case TransactionType.Payment:
|
||||||
|
return { tag: TransactionType.Payment, proposalId: rest[0] };
|
||||||
|
case TransactionType.PeerPullCredit:
|
||||||
|
return { tag: TransactionType.PeerPullCredit, pursePub: rest[0] };
|
||||||
|
case TransactionType.PeerPullDebit:
|
||||||
|
return {
|
||||||
|
tag: TransactionType.PeerPullDebit,
|
||||||
|
peerPullPaymentIncomingId: rest[0],
|
||||||
|
};
|
||||||
|
case TransactionType.PeerPushCredit:
|
||||||
|
return {
|
||||||
|
tag: TransactionType.PeerPushCredit,
|
||||||
|
peerPushPaymentIncomingId: rest[0],
|
||||||
|
};
|
||||||
|
case TransactionType.PeerPushDebit:
|
||||||
|
return { tag: TransactionType.PeerPushDebit, pursePub: rest[0] };
|
||||||
|
case TransactionType.Refresh:
|
||||||
|
return { tag: TransactionType.Refresh, refreshGroupId: rest[0] };
|
||||||
|
case TransactionType.Refund:
|
||||||
|
return {
|
||||||
|
tag: TransactionType.Refund,
|
||||||
|
proposalId: rest[0],
|
||||||
|
executionTime: rest[1],
|
||||||
|
};
|
||||||
|
case TransactionType.Tip:
|
||||||
|
return {
|
||||||
|
tag: TransactionType.Tip,
|
||||||
|
walletTipId: rest[0],
|
||||||
|
};
|
||||||
|
case TransactionType.Withdrawal:
|
||||||
|
return {
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId: rest[0],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopLongpolling(ws: InternalWalletState, taskId: string) {
|
||||||
|
const longpoll = ws.activeLongpoll[taskId];
|
||||||
|
if (longpoll) {
|
||||||
|
logger.info(`cancelling long-polling for ${taskId}`);
|
||||||
|
longpoll.cancel();
|
||||||
|
delete ws.activeLongpoll[taskId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immediately retry the underlying operation
|
* Immediately retry the underlying operation
|
||||||
* of a transaction.
|
* of a transaction.
|
||||||
@ -1369,34 +1445,86 @@ export async function retryTransaction(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.info(`retrying transaction ${transactionId}`);
|
logger.info(`retrying transaction ${transactionId}`);
|
||||||
|
|
||||||
const { type, args: rest } = parseId("any", transactionId);
|
const parsedTx = parseTransactionIdentifier(transactionId);
|
||||||
|
|
||||||
switch (type) {
|
if (!parsedTx) {
|
||||||
case TransactionType.Deposit: {
|
throw Error("invalid transaction identifier");
|
||||||
const depositGroupId = rest[0];
|
}
|
||||||
processDepositGroup(ws, depositGroupId, {
|
|
||||||
forceNow: true,
|
// FIXME: We currently don't cancel active long-polling tasks here.
|
||||||
|
|
||||||
|
switch (parsedTx.tag) {
|
||||||
|
case TransactionType.PeerPullCredit: {
|
||||||
|
const taskId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.PeerPullInitiation,
|
||||||
|
pursePub: parsedTx.pursePub,
|
||||||
});
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processPeerPullCredit(ws, parsedTx.pursePub),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TransactionType.Deposit: {
|
||||||
|
const taskId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Deposit,
|
||||||
|
depositGroupId: parsedTx.depositGroupId,
|
||||||
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processDepositGroup(ws, parsedTx.depositGroupId),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionType.Withdrawal: {
|
case TransactionType.Withdrawal: {
|
||||||
const withdrawalGroupId = rest[0];
|
// FIXME: Abort current long-poller!
|
||||||
await processWithdrawalGroup(ws, withdrawalGroupId, { forceNow: true });
|
const taskId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Withdraw,
|
||||||
|
withdrawalGroupId: parsedTx.withdrawalGroupId,
|
||||||
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processWithdrawalGroup(ws, parsedTx.withdrawalGroupId),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionType.Payment: {
|
case TransactionType.Payment: {
|
||||||
const proposalId = rest[0];
|
const taskId = constructTaskIdentifier({
|
||||||
await processPurchasePay(ws, proposalId, { forceNow: true });
|
tag: PendingTaskType.Purchase,
|
||||||
|
proposalId: parsedTx.proposalId,
|
||||||
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processPurchasePay(ws, parsedTx.proposalId),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionType.Tip: {
|
case TransactionType.Tip: {
|
||||||
const walletTipId = rest[0];
|
const taskId = constructTaskIdentifier({
|
||||||
await processTip(ws, walletTipId, { forceNow: true });
|
tag: PendingTaskType.TipPickup,
|
||||||
|
walletTipId: parsedTx.walletTipId,
|
||||||
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processTip(ws, parsedTx.walletTipId),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionType.Refresh: {
|
case TransactionType.Refresh: {
|
||||||
const refreshGroupId = rest[0];
|
const taskId = constructTaskIdentifier({
|
||||||
await processRefreshGroup(ws, refreshGroupId, { forceNow: true });
|
tag: PendingTaskType.Refresh,
|
||||||
|
refreshGroupId: parsedTx.refreshGroupId,
|
||||||
|
});
|
||||||
|
await resetOperationTimeout(ws, taskId);
|
||||||
|
stopLongpolling(ws, taskId);
|
||||||
|
await runOperationWithErrorReporting(ws, taskId, () =>
|
||||||
|
processRefreshGroup(ws, parsedTx.refreshGroupId),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -109,7 +109,7 @@ import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
|
|||||||
import {
|
import {
|
||||||
OperationAttemptResult,
|
OperationAttemptResult,
|
||||||
OperationAttemptResultType,
|
OperationAttemptResultType,
|
||||||
RetryTags,
|
TaskIdentifiers,
|
||||||
} from "../util/retries.js";
|
} from "../util/retries.js";
|
||||||
import {
|
import {
|
||||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||||
@ -1023,7 +1023,6 @@ export async function processWithdrawalGroup(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
withdrawalGroupId: string,
|
withdrawalGroupId: string,
|
||||||
options: {
|
options: {
|
||||||
forceNow?: boolean;
|
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<OperationAttemptResult> {
|
): Promise<OperationAttemptResult> {
|
||||||
logger.trace("processing withdrawal group", withdrawalGroupId);
|
logger.trace("processing withdrawal group", withdrawalGroupId);
|
||||||
@ -1037,10 +1036,10 @@ export async function processWithdrawalGroup(
|
|||||||
throw Error(`withdrawal group ${withdrawalGroupId} not found`);
|
throw Error(`withdrawal group ${withdrawalGroupId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const retryTag = RetryTags.forWithdrawal(withdrawalGroup);
|
const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup);
|
||||||
|
|
||||||
// We're already running!
|
// We're already running!
|
||||||
if (ws.activeLongpoll[retryTag] && !options.forceNow) {
|
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: OperationAttemptResultType.Longpoll,
|
||||||
@ -1532,7 +1531,7 @@ export async function getWithdrawalDetailsForUri(
|
|||||||
.iter(r.baseUrl)
|
.iter(r.baseUrl)
|
||||||
.toArray();
|
.toArray();
|
||||||
const retryRecord = await tx.operationRetries.get(
|
const retryRecord = await tx.operationRetries.get(
|
||||||
RetryTags.forExchangeUpdate(r),
|
TaskIdentifiers.forExchangeUpdate(r),
|
||||||
);
|
);
|
||||||
if (exchangeDetails && denominations) {
|
if (exchangeDetails && denominations) {
|
||||||
exchanges.push(
|
exchanges.push(
|
||||||
@ -2087,7 +2086,7 @@ export async function createManualWithdrawal(
|
|||||||
// rely on retry handling to re-process the withdrawal group.
|
// rely on retry handling to re-process the withdrawal group.
|
||||||
runOperationWithErrorReporting(
|
runOperationWithErrorReporting(
|
||||||
ws,
|
ws,
|
||||||
RetryTags.forWithdrawal(withdrawalGroup),
|
TaskIdentifiers.forWithdrawal(withdrawalGroup),
|
||||||
async () => {
|
async () => {
|
||||||
return await processWithdrawalGroup(ws, withdrawalGroupId, {
|
return await processWithdrawalGroup(ws, withdrawalGroupId, {
|
||||||
forceNow: true,
|
forceNow: true,
|
||||||
|
@ -46,6 +46,7 @@ import { TalerError } from "@gnu-taler/taler-util";
|
|||||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||||
import { PendingTaskType } from "../pending-types.js";
|
import { PendingTaskType } from "../pending-types.js";
|
||||||
import { GetReadWriteAccess } from "./query.js";
|
import { GetReadWriteAccess } from "./query.js";
|
||||||
|
import { assertUnreachable } from "./assertUnreachable.js";
|
||||||
|
|
||||||
const logger = new Logger("util/retries.ts");
|
const logger = new Logger("util/retries.ts");
|
||||||
|
|
||||||
@ -176,7 +177,66 @@ export namespace RetryInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace RetryTags {
|
/**
|
||||||
|
* Parsed representation of task identifiers.
|
||||||
|
*/
|
||||||
|
export type ParsedTaskIdentifier =
|
||||||
|
| {
|
||||||
|
tag: PendingTaskType.Withdraw;
|
||||||
|
withdrawalGroupId: string;
|
||||||
|
}
|
||||||
|
| { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string }
|
||||||
|
| { tag: PendingTaskType.Backup; backupProviderBaseUrl: string }
|
||||||
|
| { tag: PendingTaskType.Deposit; depositGroupId: string }
|
||||||
|
| { tag: PendingTaskType.ExchangeCheckRefresh; exchangeBaseUrl: string }
|
||||||
|
| { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string }
|
||||||
|
| { tag: PendingTaskType.PeerPullDebit; peerPullPaymentIncomingId: string }
|
||||||
|
| { tag: PendingTaskType.PeerPullInitiation; pursePub: string }
|
||||||
|
| { tag: PendingTaskType.PeerPushCredit; peerPushPaymentIncomingId: string }
|
||||||
|
| { tag: PendingTaskType.PeerPushInitiation; pursePub: string }
|
||||||
|
| { tag: PendingTaskType.Purchase; proposalId: string }
|
||||||
|
| { tag: PendingTaskType.Recoup; recoupGroupId: string }
|
||||||
|
| { tag: PendingTaskType.TipPickup; walletTipId: string }
|
||||||
|
| { tag: PendingTaskType.Refresh; refreshGroupId: string };
|
||||||
|
|
||||||
|
export function parseTaskIdentifier(x: string): ParsedTaskIdentifier {
|
||||||
|
throw Error("not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructTaskIdentifier(p: ParsedTaskIdentifier): string {
|
||||||
|
switch (p.tag) {
|
||||||
|
case PendingTaskType.Backup:
|
||||||
|
return `${p.tag}:${p.backupProviderBaseUrl}`;
|
||||||
|
case PendingTaskType.Deposit:
|
||||||
|
return `${p.tag}:${p.depositGroupId}`;
|
||||||
|
case PendingTaskType.ExchangeCheckRefresh:
|
||||||
|
return `${p.tag}:${p.exchangeBaseUrl}`;
|
||||||
|
case PendingTaskType.ExchangeUpdate:
|
||||||
|
return `${p.tag}:${p.exchangeBaseUrl}`;
|
||||||
|
case PendingTaskType.PeerPullDebit:
|
||||||
|
return `${p.tag}:${p.peerPullPaymentIncomingId}`;
|
||||||
|
case PendingTaskType.PeerPushCredit:
|
||||||
|
return `${p.tag}:${p.peerPushPaymentIncomingId}`;
|
||||||
|
case PendingTaskType.PeerPullInitiation:
|
||||||
|
return `${p.tag}:${p.pursePub}`;
|
||||||
|
case PendingTaskType.PeerPushInitiation:
|
||||||
|
return `${p.tag}:${p.pursePub}`;
|
||||||
|
case PendingTaskType.Purchase:
|
||||||
|
return `${p.tag}:${p.proposalId}`;
|
||||||
|
case PendingTaskType.Recoup:
|
||||||
|
return `${p.tag}:${p.recoupGroupId}`;
|
||||||
|
case PendingTaskType.Refresh:
|
||||||
|
return `${p.tag}:${p.refreshGroupId}`;
|
||||||
|
case PendingTaskType.TipPickup:
|
||||||
|
return `${p.tag}:${p.walletTipId}`;
|
||||||
|
case PendingTaskType.Withdraw:
|
||||||
|
return `${p.tag}:${p.withdrawalGroupId}`;
|
||||||
|
default:
|
||||||
|
assertUnreachable(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace TaskIdentifiers {
|
||||||
export function forWithdrawal(wg: WithdrawalGroupRecord): string {
|
export function forWithdrawal(wg: WithdrawalGroupRecord): string {
|
||||||
return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}`;
|
return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}`;
|
||||||
}
|
}
|
||||||
@ -227,19 +287,6 @@ export namespace RetryTags {
|
|||||||
): string {
|
): string {
|
||||||
return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}`;
|
return `${PendingTaskType.PeerPushCredit}:${ppi.peerPushPaymentIncomingId}`;
|
||||||
}
|
}
|
||||||
export function byPaymentProposalId(proposalId: string): string {
|
|
||||||
return `${PendingTaskType.Purchase}:${proposalId}`;
|
|
||||||
}
|
|
||||||
export function byPeerPushPaymentInitiationPursePub(
|
|
||||||
pursePub: string,
|
|
||||||
): string {
|
|
||||||
return `${PendingTaskType.PeerPushInitiation}:${pursePub}`;
|
|
||||||
}
|
|
||||||
export function byPeerPullPaymentInitiationPursePub(
|
|
||||||
pursePub: string,
|
|
||||||
): string {
|
|
||||||
return `${PendingTaskType.PeerPullInitiation}:${pursePub}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scheduleRetryInTx(
|
export async function scheduleRetryInTx(
|
||||||
|
@ -265,7 +265,7 @@ import {
|
|||||||
GetReadOnlyAccess,
|
GetReadOnlyAccess,
|
||||||
GetReadWriteAccess,
|
GetReadWriteAccess,
|
||||||
} from "./util/query.js";
|
} from "./util/query.js";
|
||||||
import { OperationAttemptResult, RetryTags } from "./util/retries.js";
|
import { OperationAttemptResult, TaskIdentifiers } from "./util/retries.js";
|
||||||
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
||||||
import {
|
import {
|
||||||
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
||||||
@ -306,17 +306,15 @@ async function callOperationHandler(
|
|||||||
forceNow,
|
forceNow,
|
||||||
});
|
});
|
||||||
case PendingTaskType.Refresh:
|
case PendingTaskType.Refresh:
|
||||||
return await processRefreshGroup(ws, pending.refreshGroupId, {
|
return await processRefreshGroup(ws, pending.refreshGroupId);
|
||||||
forceNow,
|
|
||||||
});
|
|
||||||
case PendingTaskType.Withdraw:
|
case PendingTaskType.Withdraw:
|
||||||
return await processWithdrawalGroup(ws, pending.withdrawalGroupId, {
|
return await processWithdrawalGroup(ws, pending.withdrawalGroupId, {
|
||||||
forceNow,
|
forceNow,
|
||||||
});
|
});
|
||||||
case PendingTaskType.TipPickup:
|
case PendingTaskType.TipPickup:
|
||||||
return await processTip(ws, pending.tipId, { forceNow });
|
return await processTip(ws, pending.tipId);
|
||||||
case PendingTaskType.Purchase:
|
case PendingTaskType.Purchase:
|
||||||
return await processPurchase(ws, pending.proposalId, { forceNow });
|
return await processPurchase(ws, pending.proposalId);
|
||||||
case PendingTaskType.Recoup:
|
case PendingTaskType.Recoup:
|
||||||
return await processRecoupGroupHandler(ws, pending.recoupGroupId, {
|
return await processRecoupGroupHandler(ws, pending.recoupGroupId, {
|
||||||
forceNow,
|
forceNow,
|
||||||
@ -324,9 +322,7 @@ async function callOperationHandler(
|
|||||||
case PendingTaskType.ExchangeCheckRefresh:
|
case PendingTaskType.ExchangeCheckRefresh:
|
||||||
return await autoRefresh(ws, pending.exchangeBaseUrl);
|
return await autoRefresh(ws, pending.exchangeBaseUrl);
|
||||||
case PendingTaskType.Deposit: {
|
case PendingTaskType.Deposit: {
|
||||||
return await processDepositGroup(ws, pending.depositGroupId, {
|
return await processDepositGroup(ws, pending.depositGroupId);
|
||||||
forceNow,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
case PendingTaskType.Backup:
|
case PendingTaskType.Backup:
|
||||||
return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
|
return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
|
||||||
@ -691,7 +687,7 @@ async function getExchanges(
|
|||||||
for (const r of exchangeRecords) {
|
for (const r of exchangeRecords) {
|
||||||
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
|
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
|
||||||
const opRetryRecord = await tx.operationRetries.get(
|
const opRetryRecord = await tx.operationRetries.get(
|
||||||
RetryTags.forExchangeUpdate(r),
|
TaskIdentifiers.forExchangeUpdate(r),
|
||||||
);
|
);
|
||||||
exchanges.push(
|
exchanges.push(
|
||||||
makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
|
makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
|
||||||
@ -1285,9 +1281,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
RefreshReason.Manual,
|
RefreshReason.Manual,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
processRefreshGroup(ws, refreshGroupId.refreshGroupId, {
|
processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((x) => {
|
||||||
forceNow: true,
|
|
||||||
}).catch((x) => {
|
|
||||||
logger.error(x);
|
logger.error(x);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -1753,6 +1747,7 @@ class InternalWalletStateImpl implements InternalWalletState {
|
|||||||
for (const key of Object.keys(this.activeLongpoll)) {
|
for (const key of Object.keys(this.activeLongpoll)) {
|
||||||
logger.trace(`cancelling active longpoll ${key}`);
|
logger.trace(`cancelling active longpoll ${key}`);
|
||||||
this.activeLongpoll[key].cancel();
|
this.activeLongpoll[key].cancel();
|
||||||
|
delete this.activeLongpoll[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user