This commit is contained in:
Florian Dold 2023-05-05 10:56:42 +02:00
parent 1b0bec0363
commit 60805f3ff8
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
17 changed files with 761 additions and 234 deletions

View File

@ -89,7 +89,6 @@ export enum TransactionMajorState {
Aborting = "aborting", Aborting = "aborting",
Aborted = "aborted", Aborted = "aborted",
Suspended = "suspended", Suspended = "suspended",
SuspendedDeletable = "suspended-deletable",
Dialog = "dialog", Dialog = "dialog",
SuspendedAborting = "suspended-aborting", SuspendedAborting = "suspended-aborting",
Failed = "failed", Failed = "failed",
@ -105,6 +104,7 @@ export enum TransactionMinorState {
Deposit = "deposit", Deposit = "deposit",
KycRequired = "kyc", KycRequired = "kyc",
AmlRequired = "aml", AmlRequired = "aml",
MergeKycRequired = "merge-kyc",
Track = "track", Track = "track",
Pay = "pay", Pay = "pay",
RebindSession = "rebind-session", RebindSession = "rebind-session",
@ -113,8 +113,13 @@ export enum TransactionMinorState {
AutoRefund = "auto-refund", AutoRefund = "auto-refund",
User = "user", User = "user",
Bank = "bank", Bank = "bank",
Exchange = "exchange",
ClaimProposal = "claim-proposal", ClaimProposal = "claim-proposal",
CheckRefunds = "check-refunds", CheckRefunds = "check-refunds",
CreatePurse = "create-purse",
DeletePurse = "delete-purse",
Ready = "ready",
Merge = "merge",
Repurchase = "repurchase", Repurchase = "repurchase",
BankRegisterReserve = "bank-register-reserve", BankRegisterReserve = "bank-register-reserve",
BankConfirmTransfer = "bank-confirm-transfer", BankConfirmTransfer = "bank-confirm-transfer",
@ -122,6 +127,9 @@ export enum TransactionMinorState {
ExchangeWaitReserve = "exchange-wait-reserve", ExchangeWaitReserve = "exchange-wait-reserve",
AbortingBank = "aborting-bank", AbortingBank = "aborting-bank",
Refused = "refused", Refused = "refused",
Withdraw = "withdraw",
MerchantOrderProposed = "merchant-order-proposed",
Proposed = "proposed",
} }
export interface TransactionsResponse { export interface TransactionsResponse {

View File

@ -444,7 +444,7 @@ transactionsCli
}); });
transactionsCli transactionsCli
.subcommand("cancelAbortingTransaction", "suspend", { .subcommand("cancelAbortingTransaction", "cancel-aborting", {
help: "Cancel the attempt of properly aborting a transaction.", help: "Cancel the attempt of properly aborting a transaction.",
}) })
.requiredArgument("transactionId", clk.STRING, { .requiredArgument("transactionId", clk.STRING, {

View File

@ -130,10 +130,10 @@ export enum OperationStatusRange {
// Operations that need to be actively processed. // Operations that need to be actively processed.
ACTIVE_START = 10, ACTIVE_START = 10,
ACTIVE_END = 29, ACTIVE_END = 29,
// Operations that need user input, but nothing can be done // Operations that are suspended and might
// automatically. // expire, but nothing else can be done.
USER_ATTENTION_START = 30, SUSPENDED_START = 30,
USER_ATTENTION_END = 49, SUSPENDED_END = 49,
// Operations that don't need any attention or processing. // Operations that don't need any attention or processing.
DORMANT_START = 50, DORMANT_START = 50,
DORMANT_END = 69, DORMANT_END = 69,
@ -146,24 +146,24 @@ export enum WithdrawalGroupStatus {
/** /**
* Reserve must be registered with the bank. * Reserve must be registered with the bank.
*/ */
RegisteringBank = 10, PendingRegisteringBank = 10,
/** /**
* We've registered reserve's information with the bank * We've registered reserve's information with the bank
* and are now waiting for the user to confirm the withdraw * and are now waiting for the user to confirm the withdraw
* with the bank (typically 2nd factor auth). * with the bank (typically 2nd factor auth).
*/ */
WaitConfirmBank = 11, PendingWaitConfirmBank = 11,
/** /**
* Querying reserve status with the exchange. * Querying reserve status with the exchange.
*/ */
QueryingStatus = 12, PendingQueryingStatus = 12,
/** /**
* Ready for withdrawal. * Ready for withdrawal.
*/ */
Ready = 13, PendingReady = 13,
/** /**
* We are telling the bank that we don't want to complete * We are telling the bank that we don't want to complete
@ -174,12 +174,20 @@ export enum WithdrawalGroupStatus {
/** /**
* Exchange wants KYC info from the user. * Exchange wants KYC info from the user.
*/ */
Kyc = 16, PendingKyc = 16,
/** /**
* Exchange is doing AML checks. * Exchange is doing AML checks.
*/ */
Aml = 17, PendingAml = 17,
SuspendedRegisteringBank = 30,
SuspendedWaitConfirmBank = 31,
SuspendedQueryingStatus = 32,
SuspendedReady = 33,
SuspendedAbortingBank = 34,
SuspendedKyc = 35,
SuspendedAml = 36,
/** /**
* The corresponding withdraw record has been created. * The corresponding withdraw record has been created.
@ -191,16 +199,16 @@ export enum WithdrawalGroupStatus {
/** /**
* The bank aborted the withdrawal. * The bank aborted the withdrawal.
*/ */
BankAborted = 51, FailedBankAborted = 51,
SuspendedRegisteringBank = 52,
SuspendedWaitConfirmBank = 53,
SuspendedQueryingStatus = 54,
SuspendedReady = 55,
SuspendedAbortingBank = 56,
SuspendedKyc = 57,
SuspendedAml = 58,
FailedAbortingBank = 59, FailedAbortingBank = 59,
/**
* Aborted in a state where we were supposed to
* talk to the exchange. Money might have been
* wired or not.
*/
AbortedExchange = 60
} }
/** /**
@ -1790,8 +1798,18 @@ export enum PeerPushPaymentInitiationStatus {
/** /**
* Initiated, but no purse created yet. * Initiated, but no purse created yet.
*/ */
Initiated = 10 /* ACTIVE_START */, PendingCreatePurse = 10 /* ACTIVE_START */,
PurseCreated = 50 /* DORMANT_START */, PendingReady = 11,
AbortingDeletePurse = 12,
AbortingRefresh = 13,
SuspendedCreatePurse = 30,
SuspendedReady = 31,
SuspendedAbortingDeletePurse = 32,
SuspendedAbortingRefresh = 33,
Done = 50 /* DORMANT_START */,
Aborted = 51,
} }
export interface PeerPushPaymentCoinSelection { export interface PeerPushPaymentCoinSelection {
@ -1856,14 +1874,18 @@ export interface PeerPushPaymentInitiationRecord {
} }
export enum PeerPullPaymentInitiationStatus { export enum PeerPullPaymentInitiationStatus {
Initial = 10 /* ACTIVE_START */, PendingCreatePurse = 10 /* ACTIVE_START */,
/** /**
* Purse created, waiting for the other party to accept the * Purse created, waiting for the other party to accept the
* invoice and deposit money into it. * invoice and deposit money into it.
*/ */
PurseCreated = 11 /* ACTIVE_START + 1 */, PendingReady = 11 /* ACTIVE_START + 1 */,
KycRequired = 12 /* ACTIVE_START + 2 */, PendingMergeKycRequired = 12 /* ACTIVE_START + 2 */,
PurseDeposited = 50 /* DORMANT_START */, PendingWithdrawing = 13,
SuspendedCreatePurse = 30,
SuspendedReady = 31,
SuspendedWithdrawing = 32,
DonePurseDeposited = 50 /* DORMANT_START */,
} }
export interface PeerPullPaymentInitiationRecord { export interface PeerPullPaymentInitiationRecord {
@ -1921,12 +1943,13 @@ export interface PeerPullPaymentInitiationRecord {
export enum PeerPushPaymentIncomingStatus { export enum PeerPushPaymentIncomingStatus {
Proposed = 30 /* USER_ATTENTION_START */, Proposed = 30 /* USER_ATTENTION_START */,
Accepted = 10 /* ACTIVE_START */, Accepted = 10 /* ACTIVE_START */,
KycRequired = 11 /* ACTIVE_START + 1 */, MergeKycRequired = 11 /* ACTIVE_START + 1 */,
/** /**
* Merge was successful and withdrawal group has been created, now * Merge was successful and withdrawal group has been created, now
* everything is in the hand of the withdrawal group. * everything is in the hand of the withdrawal group.
*/ */
WithdrawalCreated = 50 /* DORMANT_START */, Withdrawing = 12,
Done = 50 /* DORMANT_START */,
} }
/** /**

View File

@ -177,7 +177,7 @@ export interface InternalWalletState {
* *
* Used to allow processing of new work faster. * Used to allow processing of new work faster.
*/ */
latch: AsyncCondition; workAvailable: AsyncCondition;
listeners: NotificationListener[]; listeners: NotificationListener[];

View File

@ -553,7 +553,7 @@ export async function importBackup(
reservePub, reservePub,
status: backupWg.timestamp_finish status: backupWg.timestamp_finish
? WithdrawalGroupStatus.Finished ? WithdrawalGroupStatus.Finished
: WithdrawalGroupStatus.QueryingStatus, // FIXME! : WithdrawalGroupStatus.PendingQueryingStatus, // FIXME!
timestampStart: backupWg.timestamp_created, timestampStart: backupWg.timestamp_created,
wgInfo, wgInfo,
restrictAge: backupWg.restrict_age, restrictAge: backupWg.restrict_age,

View File

@ -492,7 +492,7 @@ export function runLongpollAsync(
if (!res.ready) { if (!res.ready) {
await storeOperationPending(ws, retryTag); await storeOperationPending(ws, retryTag);
} }
ws.latch.trigger(); ws.workAvailable.trigger();
}; };
asyncFn(); asyncFn();
} }

View File

@ -248,7 +248,7 @@ export async function resumeDepositGroup(
} }
return undefined; return undefined;
}); });
ws.latch.trigger(); ws.workAvailable.trigger();
if (res) { if (res) {
ws.notify({ ws.notify({
type: NotificationType.TransactionStateTransition, type: NotificationType.TransactionStateTransition,
@ -301,7 +301,7 @@ export async function abortDepositGroup(
}); });
stopLongpolling(ws, retryTag); stopLongpolling(ws, retryTag);
// Need to process the operation again. // Need to process the operation again.
ws.latch.trigger(); ws.workAvailable.trigger();
if (res) { if (res) {
ws.notify({ ws.notify({
type: NotificationType.TransactionStateTransition, type: NotificationType.TransactionStateTransition,

View File

@ -2410,7 +2410,7 @@ export async function processPurchaseQueryRefund(
return OperationAttemptResult.finishedEmpty(); return OperationAttemptResult.finishedEmpty();
} }
export async function abortPay( export async function abortPayMerchant(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
cancelImmediately?: boolean, cancelImmediately?: boolean,
@ -2499,6 +2499,7 @@ export function computePayMerchantTransactionState(
case PurchaseStatus.Proposed: case PurchaseStatus.Proposed:
return { return {
major: TransactionMajorState.Dialog, major: TransactionMajorState.Dialog,
minor: TransactionMinorState.MerchantOrderProposed,
}; };
case PurchaseStatus.ProposalDownloadFailed: case PurchaseStatus.ProposalDownloadFailed:
return { return {

View File

@ -75,20 +75,21 @@ import {
NotificationType, NotificationType,
HttpStatusCode, HttpStatusCode,
codecForWalletKycUuid, codecForWalletKycUuid,
WalletKycUuid, TransactionState,
TransactionMajorState,
TransactionMinorState,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js"; import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
import { import {
DenominationRecord, DenominationRecord,
KycPendingInfo, PeerPullPaymentIncomingRecord,
KycUserType,
OperationStatus,
PeerPullPaymentIncomingStatus, PeerPullPaymentIncomingStatus,
PeerPullPaymentInitiationRecord, PeerPullPaymentInitiationRecord,
PeerPullPaymentInitiationStatus, PeerPullPaymentInitiationStatus,
PeerPushPaymentCoinSelection, PeerPushPaymentCoinSelection,
PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingRecord,
PeerPushPaymentIncomingStatus, PeerPushPaymentIncomingStatus,
PeerPushPaymentInitiationRecord,
PeerPushPaymentInitiationStatus, PeerPushPaymentInitiationStatus,
ReserveRecord, ReserveRecord,
WithdrawalGroupStatus, WithdrawalGroupStatus,
@ -98,7 +99,6 @@ import { TalerError } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { import {
LongpollResult, LongpollResult,
makeTransactionId,
resetOperationTimeout, resetOperationTimeout,
runLongpollAsync, runLongpollAsync,
runOperationWithErrorReporting, runOperationWithErrorReporting,
@ -128,8 +128,10 @@ import {
import { PendingTaskType } from "../pending-types.js"; import { PendingTaskType } from "../pending-types.js";
import { import {
constructTransactionIdentifier, constructTransactionIdentifier,
notifyTransition,
stopLongpolling, stopLongpolling,
} from "./transactions.js"; } from "./transactions.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
const logger = new Logger("operations/peer-to-peer.ts"); const logger = new Logger("operations/peer-to-peer.ts");
@ -451,19 +453,11 @@ export async function checkPeerPushDebit(
}; };
} }
export async function processPeerPushInitiation( async function processPeerPushDebitCreateReserve(
ws: InternalWalletState, ws: InternalWalletState,
pursePub: string, peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> { ): Promise<OperationAttemptResult> {
const peerPushInitiation = await ws.db const pursePub = peerPushInitiation.pursePub;
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadOnly(async (tx) => {
return tx.peerPushPaymentInitiations.get(pursePub);
});
if (!peerPushInitiation) {
throw Error("peer push payment not found");
}
const purseExpiration = peerPushInitiation.purseExpiration; const purseExpiration = peerPushInitiation.purseExpiration;
const hContractTerms = peerPushInitiation.contractTermsHash; const hContractTerms = peerPushInitiation.contractTermsHash;
@ -501,22 +495,25 @@ export async function processPeerPushInitiation(
peerPushInitiation.exchangeBaseUrl, peerPushInitiation.exchangeBaseUrl,
); );
const httpResp = await ws.http.postJson(createPurseUrl.href, { const httpResp = await ws.http.fetch(createPurseUrl.href, {
amount: peerPushInitiation.amount, method: "POST",
merge_pub: peerPushInitiation.mergePub, body: {
purse_sig: purseSigResp.sig, amount: peerPushInitiation.amount,
h_contract_terms: hContractTerms, merge_pub: peerPushInitiation.mergePub,
purse_expiration: purseExpiration, purse_sig: purseSigResp.sig,
deposits: depositSigsResp.deposits, h_contract_terms: hContractTerms,
min_age: 0, purse_expiration: purseExpiration,
econtract: econtractResp.econtract, deposits: depositSigsResp.deposits,
min_age: 0,
econtract: econtractResp.econtract,
},
}); });
const resp = await httpResp.json(); const resp = await httpResp.json();
logger.info(`resp: ${j2s(resp)}`); logger.info(`resp: ${j2s(resp)}`);
if (httpResp.status !== 200) { if (httpResp.status !== HttpStatusCode.Ok) {
throw Error("got error response from exchange"); throw Error("got error response from exchange");
} }
@ -527,7 +524,7 @@ export async function processPeerPushInitiation(
if (!ppi) { if (!ppi) {
return; return;
} }
ppi.status = PeerPushPaymentInitiationStatus.PurseCreated; ppi.status = PeerPushPaymentInitiationStatus.Done;
await tx.peerPushPaymentInitiations.put(ppi); await tx.peerPushPaymentInitiations.put(ppi);
}); });
@ -537,6 +534,122 @@ export async function processPeerPushInitiation(
}; };
} }
async function transitionPeerPushDebitFromReadyToDone(
ws: InternalWalletState,
pursePub: string,
): Promise<void> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub);
if (!ppiRec) {
return undefined;
}
if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) {
return undefined;
}
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
ppiRec.status = PeerPushPaymentInitiationStatus.Done;
const newTxState = computePeerPushDebitTransactionState(ppiRec);
return {
oldTxState,
newTxState,
};
});
notifyTransition(ws, transactionId, transitionInfo);
}
/**
* Process the "pending(ready)" state of a peer-push-debit transaction.
*/
async function processPeerPushDebitReady(
ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord,
): Promise<OperationAttemptResult> {
const pursePub = peerPushInitiation.pursePub;
const retryTag = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
runLongpollAsync(ws, retryTag, async (ct) => {
const mergeUrl = new URL(`purses/${pursePub}/merge`);
mergeUrl.searchParams.set("timeout_ms", "30000");
const resp = await ws.http.fetch(mergeUrl.href, {
// timeout: getReserveRequestTimeout(withdrawalGroup),
cancellationToken: ct,
});
if (resp.status === HttpStatusCode.Ok) {
const purseStatus = await readSuccessResponseJsonOrThrow(
resp,
codecForExchangePurseStatus(),
);
if (purseStatus.deposit_timestamp) {
await transitionPeerPushDebitFromReadyToDone(
ws,
peerPushInitiation.pursePub,
);
return {
ready: true,
};
}
} else if (resp.status === HttpStatusCode.Gone) {
// FIXME: transition the reserve into the expired state
}
return {
ready: false,
};
});
logger.trace(
"returning early from withdrawal for long-polling in background",
);
return {
type: OperationAttemptResultType.Longpoll,
};
}
export async function processPeerPushDebit(
ws: InternalWalletState,
pursePub: string,
): Promise<OperationAttemptResult> {
const peerPushInitiation = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadOnly(async (tx) => {
return tx.peerPushPaymentInitiations.get(pursePub);
});
if (!peerPushInitiation) {
throw Error("peer push payment not found");
}
const retryTag = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
// We're already running!
if (ws.activeLongpoll[retryTag]) {
logger.info("peer-push-debit task already in long-polling, returning!");
return {
type: OperationAttemptResultType.Longpoll,
};
}
switch (peerPushInitiation.status) {
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
return processPeerPushDebitCreateReserve(ws, peerPushInitiation);
case PeerPushPaymentInitiationStatus.PendingReady:
return processPeerPushDebitReady(ws, peerPushInitiation);
}
return {
type: OperationAttemptResultType.Finished,
result: undefined,
};
}
/** /**
* Initiate sending a peer-to-peer push payment. * Initiate sending a peer-to-peer push payment.
*/ */
@ -612,7 +725,7 @@ export async function initiatePeerPushPayment(
pursePriv: pursePair.priv, pursePriv: pursePair.priv,
pursePub: pursePair.pub, pursePub: pursePair.pub,
timestampCreated: TalerProtocolTimestamp.now(), timestampCreated: TalerProtocolTimestamp.now(),
status: PeerPushPaymentInitiationStatus.Initiated, status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms, contractTerms: contractTerms,
coinSel: { coinSel: {
coinPubs: sel.coins.map((x) => x.coinPub), coinPubs: sel.coins.map((x) => x.coinPub),
@ -628,12 +741,12 @@ export async function initiatePeerPushPayment(
}); });
const taskId = constructTaskIdentifier({ const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushInitiation, tag: PendingTaskType.PeerPushDebit,
pursePub: pursePair.pub, pursePub: pursePair.pub,
}); });
await runOperationWithErrorReporting(ws, taskId, async () => { await runOperationWithErrorReporting(ws, taskId, async () => {
return await processPeerPushInitiation(ws, pursePair.pub); return await processPeerPushDebit(ws, pursePair.pub);
}); });
return { return {
@ -645,22 +758,24 @@ export async function initiatePeerPushPayment(
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl, exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
contractPriv: contractKeyPair.priv, contractPriv: contractKeyPair.priv,
}), }),
transactionId: makeTransactionId( transactionId: constructTransactionIdentifier({
TransactionType.PeerPushDebit, tag: TransactionType.PeerPushDebit,
pursePair.pub, pursePub: pursePair.pub,
), }),
}; };
} }
interface ExchangePurseStatus { interface ExchangePurseStatus {
balance: AmountString; balance: AmountString;
deposit_timestamp?: TalerProtocolTimestamp; deposit_timestamp?: TalerProtocolTimestamp;
merge_timestamp?: TalerProtocolTimestamp;
} }
export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> => export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
buildCodecForObject<ExchangePurseStatus>() buildCodecForObject<ExchangePurseStatus>()
.property("balance", codecForAmountString()) .property("balance", codecForAmountString())
.property("deposit_timestamp", codecOptional(codecForTimestamp)) .property("deposit_timestamp", codecOptional(codecForTimestamp))
.property("merge_timestamp", codecOptional(codecForTimestamp))
.build("ExchangePurseStatus"); .build("ExchangePurseStatus");
export async function preparePeerPushCredit( export async function preparePeerPushCredit(
@ -879,13 +994,13 @@ export async function processPeerPushCredit(
const amount = Amounts.parseOrThrow(contractTerms.amount); const amount = Amounts.parseOrThrow(contractTerms.amount);
if ( if (
peerInc.status === PeerPushPaymentIncomingStatus.KycRequired && peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired &&
peerInc.kycInfo peerInc.kycInfo
) { ) {
const txId = makeTransactionId( const txId = constructTransactionIdentifier({
TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerInc.peerPushPaymentIncomingId, peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId,
); });
await checkWithdrawalKycStatus( await checkWithdrawalKycStatus(
ws, ws,
peerInc.exchangeBaseUrl, peerInc.exchangeBaseUrl,
@ -951,7 +1066,7 @@ export async function processPeerPushCredit(
paytoHash: kycPending.h_payto, paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row, requirementRow: kycPending.requirement_row,
}; };
peerInc.status = PeerPushPaymentIncomingStatus.KycRequired; peerInc.status = PeerPushPaymentIncomingStatus.MergeKycRequired;
await tx.peerPushPaymentIncoming.put(peerInc); await tx.peerPushPaymentIncoming.put(peerInc);
}); });
return { return {
@ -975,7 +1090,7 @@ export async function processPeerPushCredit(
}, },
forcedWithdrawalGroupId: peerInc.withdrawalGroupId, forcedWithdrawalGroupId: peerInc.withdrawalGroupId,
exchangeBaseUrl: peerInc.exchangeBaseUrl, exchangeBaseUrl: peerInc.exchangeBaseUrl,
reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: { reserveKeyPair: {
priv: mergeReserveInfo.reservePriv, priv: mergeReserveInfo.reservePriv,
pub: mergeReserveInfo.reservePub, pub: mergeReserveInfo.reservePub,
@ -993,9 +1108,9 @@ export async function processPeerPushCredit(
} }
if ( if (
peerInc.status === PeerPushPaymentIncomingStatus.Accepted || peerInc.status === PeerPushPaymentIncomingStatus.Accepted ||
peerInc.status === PeerPushPaymentIncomingStatus.KycRequired peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired
) { ) {
peerInc.status = PeerPushPaymentIncomingStatus.WithdrawalCreated; peerInc.status = PeerPushPaymentIncomingStatus.Done;
} }
await tx.peerPushPaymentIncoming.put(peerInc); await tx.peerPushPaymentIncoming.put(peerInc);
}); });
@ -1011,7 +1126,7 @@ export async function confirmPeerPushCredit(
req: ConfirmPeerPushCreditRequest, req: ConfirmPeerPushCreditRequest,
): Promise<AcceptPeerPushPaymentResponse> { ): Promise<AcceptPeerPushPaymentResponse> {
let peerInc: PeerPushPaymentIncomingRecord | undefined; let peerInc: PeerPushPaymentIncomingRecord | undefined;
let contractTerms: PeerContractTerms | undefined;
await ws.db await ws.db
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
@ -1021,10 +1136,6 @@ export async function confirmPeerPushCredit(
if (!peerInc) { if (!peerInc) {
return; return;
} }
const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
if (ctRec) {
contractTerms = ctRec.contractTermsRaw;
}
if (peerInc.status === PeerPushPaymentIncomingStatus.Proposed) { if (peerInc.status === PeerPushPaymentIncomingStatus.Proposed) {
peerInc.status = PeerPushPaymentIncomingStatus.Accepted; peerInc.status = PeerPushPaymentIncomingStatus.Accepted;
} }
@ -1037,21 +1148,15 @@ export async function confirmPeerPushCredit(
); );
} }
checkDbInvariant(!!contractTerms); ws.workAvailable.trigger();
await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl); const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
const retryTag = TaskIdentifiers.forPeerPushCredit(peerInc); peerPushPaymentIncomingId: req.peerPushPaymentIncomingId,
});
await runOperationWithErrorReporting(ws, retryTag, () =>
processPeerPushCredit(ws, req.peerPushPaymentIncomingId),
);
return { return {
transactionId: makeTransactionId( transactionId,
TransactionType.PeerPushCredit,
req.peerPushPaymentIncomingId,
),
}; };
} }
@ -1209,11 +1314,13 @@ export async function confirmPeerPullDebit(
}, },
); );
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId: req.peerPullPaymentIncomingId,
});
return { return {
transactionId: makeTransactionId( transactionId,
TransactionType.PeerPullDebit,
req.peerPullPaymentIncomingId,
),
}; };
} }
@ -1395,7 +1502,7 @@ export async function queryPurseForPeerPullCredit(
}, },
forcedWithdrawalGroupId: pullIni.withdrawalGroupId, forcedWithdrawalGroupId: pullIni.withdrawalGroupId,
exchangeBaseUrl: pullIni.exchangeBaseUrl, exchangeBaseUrl: pullIni.exchangeBaseUrl,
reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: { reserveKeyPair: {
priv: reserve.reservePriv, priv: reserve.reservePriv,
pub: reserve.reservePub, pub: reserve.reservePub,
@ -1410,8 +1517,8 @@ export async function queryPurseForPeerPullCredit(
logger.warn("peerPullPaymentInitiation not found anymore"); logger.warn("peerPullPaymentInitiation not found anymore");
return; return;
} }
if (finPi.status === PeerPullPaymentInitiationStatus.PurseCreated) { if (finPi.status === PeerPullPaymentInitiationStatus.PendingReady) {
finPi.status = PeerPullPaymentInitiationStatus.PurseDeposited; finPi.status = PeerPullPaymentInitiationStatus.DonePurseDeposited;
} }
await tx.peerPullPaymentInitiations.put(finPi); await tx.peerPullPaymentInitiations.put(finPi);
}); });
@ -1434,7 +1541,7 @@ export async function processPeerPullCredit(
} }
const retryTag = constructTaskIdentifier({ const retryTag = constructTaskIdentifier({
tag: PendingTaskType.PeerPullInitiation, tag: PendingTaskType.PeerPullCredit,
pursePub, pursePub,
}); });
@ -1449,7 +1556,7 @@ export async function processPeerPullCredit(
logger.trace(`processing ${retryTag}, status=${pullIni.status}`); logger.trace(`processing ${retryTag}, status=${pullIni.status}`);
switch (pullIni.status) { switch (pullIni.status) {
case PeerPullPaymentInitiationStatus.PurseDeposited: { case PeerPullPaymentInitiationStatus.DonePurseDeposited: {
// We implement this case so that the "retry" action on a peer-pull-credit transaction // We implement this case so that the "retry" action on a peer-pull-credit transaction
// also retries the withdrawal task. // also retries the withdrawal task.
@ -1475,7 +1582,7 @@ export async function processPeerPullCredit(
result: undefined, result: undefined,
}; };
} }
case PeerPullPaymentInitiationStatus.PurseCreated: case PeerPullPaymentInitiationStatus.PendingReady:
runLongpollAsync(ws, retryTag, async (cancellationToken) => runLongpollAsync(ws, retryTag, async (cancellationToken) =>
queryPurseForPeerPullCredit(ws, pullIni, cancellationToken), queryPurseForPeerPullCredit(ws, pullIni, cancellationToken),
); );
@ -1485,23 +1592,23 @@ export async function processPeerPullCredit(
return { return {
type: OperationAttemptResultType.Longpoll, type: OperationAttemptResultType.Longpoll,
}; };
case PeerPullPaymentInitiationStatus.KycRequired: { case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub: pullIni.pursePub,
});
if (pullIni.kycInfo) { if (pullIni.kycInfo) {
const txId = makeTransactionId(
TransactionType.PeerPullCredit,
pullIni.pursePub,
);
await checkWithdrawalKycStatus( await checkWithdrawalKycStatus(
ws, ws,
pullIni.exchangeBaseUrl, pullIni.exchangeBaseUrl,
txId, transactionId,
pullIni.kycInfo, pullIni.kycInfo,
"individual", "individual",
); );
} }
break; break;
} }
case PeerPullPaymentInitiationStatus.Initial: case PeerPullPaymentInitiationStatus.PendingCreatePurse:
break; break;
default: default:
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`); throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
@ -1590,7 +1697,8 @@ export async function processPeerPullCredit(
paytoHash: kycPending.h_payto, paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row, requirementRow: kycPending.requirement_row,
}; };
peerIni.status = PeerPullPaymentInitiationStatus.KycRequired; peerIni.status =
PeerPullPaymentInitiationStatus.PendingMergeKycRequired;
await tx.peerPullPaymentInitiations.put(peerIni); await tx.peerPullPaymentInitiations.put(peerIni);
}); });
return { return {
@ -1610,7 +1718,7 @@ export async function processPeerPullCredit(
if (!pi2) { if (!pi2) {
return; return;
} }
pi2.status = PeerPullPaymentInitiationStatus.PurseCreated; pi2.status = PeerPullPaymentInitiationStatus.PendingReady;
await tx.peerPullPaymentInitiations.put(pi2); await tx.peerPullPaymentInitiations.put(pi2);
}); });
@ -1776,7 +1884,7 @@ export async function initiatePeerPullPayment(
pursePub: pursePair.pub, pursePub: pursePair.pub,
mergePriv: mergePair.priv, mergePriv: mergePair.priv,
mergePub: mergePair.pub, mergePub: mergePair.pub,
status: PeerPullPaymentInitiationStatus.Initial, status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms, contractTerms: contractTerms,
mergeTimestamp, mergeTimestamp,
mergeReserveRowId: mergeReserveRowId, mergeReserveRowId: mergeReserveRowId,
@ -1796,7 +1904,7 @@ export async function initiatePeerPullPayment(
// check this asynchronously from the transaction status? // check this asynchronously from the transaction status?
const taskId = constructTaskIdentifier({ const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullInitiation, tag: PendingTaskType.PeerPullCredit,
pursePub: pursePair.pub, pursePub: pursePair.pub,
}); });
@ -1804,14 +1912,408 @@ export async function initiatePeerPullPayment(
return processPeerPullCredit(ws, pursePair.pub); return processPeerPullCredit(ws, pursePair.pub);
}); });
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub: pursePair.pub,
});
return { return {
talerUri: constructPayPullUri({ talerUri: constructPayPullUri({
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: exchangeBaseUrl,
contractPriv: contractKeyPair.priv, contractPriv: contractKeyPair.priv,
}), }),
transactionId: makeTransactionId( transactionId,
TransactionType.PeerPullCredit,
pursePair.pub,
),
}; };
} }
export function computePeerPushDebitTransactionState(
ppiRecord: PeerPushPaymentInitiationRecord,
): TransactionState {
switch (ppiRecord.status) {
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.CreatePurse,
};
case PeerPushPaymentInitiationStatus.PendingReady:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Ready,
};
case PeerPushPaymentInitiationStatus.Aborted:
return {
major: TransactionMajorState.Aborted,
};
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.DeletePurse,
};
case PeerPushPaymentInitiationStatus.AbortingRefresh:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.Refresh,
};
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
return {
major: TransactionMajorState.SuspendedAborting,
minor: TransactionMinorState.DeletePurse,
};
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
return {
major: TransactionMajorState.SuspendedAborting,
minor: TransactionMinorState.Refresh,
};
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
return {
major: TransactionMajorState.Suspended,
minor: TransactionMinorState.CreatePurse,
};
case PeerPushPaymentInitiationStatus.SuspendedReady:
return {
major: TransactionMajorState.Suspended,
minor: TransactionMinorState.Ready,
};
case PeerPushPaymentInitiationStatus.Done:
return {
major: TransactionMajorState.Done,
};
}
}
export async function abortPeerPushDebitTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
if (!pushDebitRec) {
logger.warn(`peer push debit ${pursePub} not found`);
return;
}
let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
switch (pushDebitRec.status) {
case PeerPushPaymentInitiationStatus.PendingReady:
case PeerPushPaymentInitiationStatus.SuspendedReady:
newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
break;
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
// Network request might already be in-flight!
newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
break;
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
case PeerPushPaymentInitiationStatus.AbortingRefresh:
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
case PeerPushPaymentInitiationStatus.Aborted:
// Do nothing
break;
default:
assertUnreachable(pushDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
pushDebitRec.status = newStatus;
const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
await tx.peerPushPaymentInitiations.put(pushDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function cancelAbortingPeerPushDebitTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
if (!pushDebitRec) {
logger.warn(`peer push debit ${pursePub} not found`);
return;
}
let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
switch (pushDebitRec.status) {
case PeerPushPaymentInitiationStatus.AbortingRefresh:
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
// FIXME: We also need to abort the refresh group!
newStatus = PeerPushPaymentInitiationStatus.Aborted;
break;
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
newStatus = PeerPushPaymentInitiationStatus.Aborted;
break;
case PeerPushPaymentInitiationStatus.PendingReady:
case PeerPushPaymentInitiationStatus.SuspendedReady:
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
// Do nothing
break;
default:
assertUnreachable(pushDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
pushDebitRec.status = newStatus;
const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
await tx.peerPushPaymentInitiations.put(pushDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function suspendPeerPushDebitTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
if (!pushDebitRec) {
logger.warn(`peer push debit ${pursePub} not found`);
return;
}
let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
switch (pushDebitRec.status) {
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
newStatus = PeerPushPaymentInitiationStatus.SuspendedCreatePurse;
break;
case PeerPushPaymentInitiationStatus.AbortingRefresh:
newStatus = PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh;
break;
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
newStatus =
PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse;
break;
case PeerPushPaymentInitiationStatus.PendingReady:
newStatus = PeerPushPaymentInitiationStatus.SuspendedReady;
break;
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
case PeerPushPaymentInitiationStatus.SuspendedReady:
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
// Do nothing
break;
default:
assertUnreachable(pushDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
pushDebitRec.status = newStatus;
const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
await tx.peerPushPaymentInitiations.put(pushDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function resumePeerPushDebitTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushDebit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const pushDebitRec = await tx.peerPushPaymentInitiations.get(pursePub);
if (!pushDebitRec) {
logger.warn(`peer push debit ${pursePub} not found`);
return;
}
let newStatus: PeerPushPaymentInitiationStatus | undefined = undefined;
switch (pushDebitRec.status) {
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
newStatus = PeerPushPaymentInitiationStatus.AbortingDeletePurse;
break;
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
newStatus = PeerPushPaymentInitiationStatus.AbortingRefresh;
break;
case PeerPushPaymentInitiationStatus.SuspendedReady:
newStatus = PeerPushPaymentInitiationStatus.PendingReady;
break;
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
newStatus = PeerPushPaymentInitiationStatus.PendingCreatePurse;
break;
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
case PeerPushPaymentInitiationStatus.AbortingRefresh:
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
case PeerPushPaymentInitiationStatus.PendingReady:
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
// Do nothing
break;
default:
assertUnreachable(pushDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushDebitTransactionState(pushDebitRec);
pushDebitRec.status = newStatus;
const newTxState = computePeerPushDebitTransactionState(pushDebitRec);
await tx.peerPushPaymentInitiations.put(pushDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export function computePeerPushCreditTransactionState(
pushCreditRecord: PeerPushPaymentIncomingRecord,
): TransactionState {
switch (pushCreditRecord.status) {
case PeerPushPaymentIncomingStatus.Proposed:
return {
major: TransactionMajorState.Dialog,
minor: TransactionMinorState.Proposed,
};
case PeerPushPaymentIncomingStatus.Accepted:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Merge,
};
case PeerPushPaymentIncomingStatus.Done:
return {
major: TransactionMajorState.Done,
};
case PeerPushPaymentIncomingStatus.MergeKycRequired:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.KycRequired,
};
case PeerPushPaymentIncomingStatus.Withdrawing:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Withdraw,
};
}
}
export function computePeerPullCreditTransactionState(
pullCreditRecord: PeerPullPaymentInitiationRecord,
): TransactionState {
switch (pullCreditRecord.status) {
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.CreatePurse,
};
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.MergeKycRequired,
};
case PeerPullPaymentInitiationStatus.PendingReady:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Ready,
};
case PeerPullPaymentInitiationStatus.DonePurseDeposited:
return {
major: TransactionMajorState.Done,
};
case PeerPullPaymentInitiationStatus.PendingWithdrawing:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Withdraw,
};
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
return {
major: TransactionMajorState.Suspended,
minor: TransactionMinorState.CreatePurse,
};
case PeerPullPaymentInitiationStatus.SuspendedReady:
return {
major: TransactionMajorState.Suspended,
minor: TransactionMinorState.Ready,
};
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Withdraw,
};
}
}
export function computePeerPullDebitTransactionState(
pullDebitRecord: PeerPullPaymentIncomingRecord,
): TransactionState {
switch (pullDebitRecord.status) {
case PeerPullPaymentIncomingStatus.Proposed:
return {
major: TransactionMajorState.Dialog,
minor: TransactionMinorState.Proposed,
};
case PeerPullPaymentIncomingStatus.Accepted:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Deposit,
};
case PeerPullPaymentIncomingStatus.Paid:
return {
major: TransactionMajorState.Done,
};
}
}

View File

@ -364,14 +364,14 @@ async function gatherPeerPullInitiationPending(
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => { await tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => {
if (pi.status === PeerPullPaymentInitiationStatus.PurseDeposited) { if (pi.status === PeerPullPaymentInitiationStatus.DonePurseDeposited) {
return; return;
} }
const opId = TaskIdentifiers.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({
type: PendingTaskType.PeerPullInitiation, type: PendingTaskType.PeerPullCredit,
...getPendingCommon(ws, opId, timestampDue), ...getPendingCommon(ws, opId, timestampDue),
givesLifeness: true, givesLifeness: true,
retryInfo: retryRecord?.retryInfo, retryInfo: retryRecord?.retryInfo,
@ -421,14 +421,14 @@ async function gatherPeerPushInitiationPending(
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
await tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => { await tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => {
if (pi.status === PeerPushPaymentInitiationStatus.PurseCreated) { if (pi.status === PeerPushPaymentInitiationStatus.Done) {
return; return;
} }
const opId = TaskIdentifiers.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({
type: PendingTaskType.PeerPushInitiation, type: PendingTaskType.PeerPushDebit,
...getPendingCommon(ws, opId, timestampDue), ...getPendingCommon(ws, opId, timestampDue),
givesLifeness: true, givesLifeness: true,
retryInfo: retryRecord?.retryInfo, retryInfo: retryRecord?.retryInfo,
@ -450,7 +450,7 @@ async function gatherPeerPushCreditPending(
switch (pi.status) { switch (pi.status) {
case PeerPushPaymentIncomingStatus.Proposed: case PeerPushPaymentIncomingStatus.Proposed:
return; return;
case PeerPushPaymentIncomingStatus.WithdrawalCreated: case PeerPushPaymentIncomingStatus.Done:
return; return;
} }
const opId = TaskIdentifiers.forPeerPushCredit(pi); const opId = TaskIdentifiers.forPeerPushCredit(pi);

View File

@ -400,7 +400,7 @@ export async function processRecoupGroupHandler(
await internalCreateWithdrawalGroup(ws, { await internalCreateWithdrawalGroup(ws, {
amount: Amounts.parseOrThrow(result.balance), amount: Amounts.parseOrThrow(result.balance),
exchangeBaseUrl: recoupGroup.exchangeBaseUrl, exchangeBaseUrl: recoupGroup.exchangeBaseUrl,
reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: { reserveKeyPair: {
pub: reservePub, pub: reservePub,
priv: reservePrivMap[reservePub], priv: reservePrivMap[reservePub],

View File

@ -1125,7 +1125,7 @@ export async function autoRefresh(
return OperationAttemptResult.finishedEmpty(); return OperationAttemptResult.finishedEmpty();
} }
export function computeRefreshTransactionStatus( export function computeRefreshTransactionState(
rg: RefreshGroupRecord, rg: RefreshGroupRecord,
): TransactionState { ): TransactionState {
switch (rg.operationStatus) { switch (rg.operationStatus) {
@ -1170,7 +1170,7 @@ export async function suspendRefreshGroup(
); );
return undefined; return undefined;
} }
const oldState = computeRefreshTransactionStatus(dg); const oldState = computeRefreshTransactionState(dg);
switch (dg.operationStatus) { switch (dg.operationStatus) {
case RefreshOperationStatus.Finished: case RefreshOperationStatus.Finished:
return undefined; return undefined;
@ -1179,7 +1179,7 @@ export async function suspendRefreshGroup(
await tx.refreshGroups.put(dg); await tx.refreshGroups.put(dg);
return { return {
oldTxState: oldState, oldTxState: oldState,
newTxState: computeRefreshTransactionStatus(dg), newTxState: computeRefreshTransactionState(dg),
}; };
} }
case RefreshOperationStatus.Suspended: case RefreshOperationStatus.Suspended:
@ -1215,7 +1215,7 @@ export async function resumeRefreshGroup(
); );
return; return;
} }
const oldState = computeRefreshTransactionStatus(dg); const oldState = computeRefreshTransactionState(dg);
switch (dg.operationStatus) { switch (dg.operationStatus) {
case RefreshOperationStatus.Finished: case RefreshOperationStatus.Finished:
return; return;
@ -1227,12 +1227,12 @@ export async function resumeRefreshGroup(
await tx.refreshGroups.put(dg); await tx.refreshGroups.put(dg);
return { return {
oldTxState: oldState, oldTxState: oldState,
newTxState: computeRefreshTransactionStatus(dg), newTxState: computeRefreshTransactionState(dg),
}; };
} }
return undefined; return undefined;
}); });
ws.latch.trigger(); ws.workAvailable.trigger();
if (res) { if (res) {
ws.notify({ ws.notify({
type: NotificationType.TransactionStateTransition, type: NotificationType.TransactionStateTransition,

View File

@ -87,14 +87,14 @@ import {
} from "./deposits.js"; } from "./deposits.js";
import { getExchangeDetails } from "./exchanges.js"; import { getExchangeDetails } from "./exchanges.js";
import { import {
abortPay, abortPayMerchant,
computePayMerchantTransactionState, computePayMerchantTransactionState,
expectProposalDownload, expectProposalDownload,
extractContractData, extractContractData,
processPurchasePay, processPurchasePay,
} from "./pay-merchant.js"; } from "./pay-merchant.js";
import { processPeerPullCredit } from "./pay-peer.js"; import { computePeerPullCreditTransactionState, computePeerPullDebitTransactionState, computePeerPushCreditTransactionState, computePeerPushDebitTransactionState, processPeerPullCredit } from "./pay-peer.js";
import { processRefreshGroup } from "./refresh.js"; import { computeRefreshTransactionState, processRefreshGroup } from "./refresh.js";
import { computeTipTransactionStatus, processTip } from "./tip.js"; import { computeTipTransactionStatus, processTip } from "./tip.js";
import { import {
abortWithdrawalTransaction, abortWithdrawalTransaction,
@ -445,7 +445,7 @@ function buildTransactionForPushPaymentDebit(
): Transaction { ): Transaction {
return { return {
type: TransactionType.PeerPushDebit, type: TransactionType.PeerPushDebit,
txState: mkTxStateUnknown(), txState: computePeerPushDebitTransactionState(pi),
amountEffective: pi.totalCost, amountEffective: pi.totalCost,
amountRaw: pi.amount, amountRaw: pi.amount,
exchangeBaseUrl: pi.exchangeBaseUrl, exchangeBaseUrl: pi.exchangeBaseUrl,
@ -455,10 +455,10 @@ function buildTransactionForPushPaymentDebit(
}, },
frozen: false, frozen: false,
extendedStatus: extendedStatus:
pi.status != PeerPushPaymentInitiationStatus.PurseCreated pi.status != PeerPushPaymentInitiationStatus.Done
? ExtendedStatus.Pending ? ExtendedStatus.Pending
: ExtendedStatus.Done, : ExtendedStatus.Done,
pending: pi.status != PeerPushPaymentInitiationStatus.PurseCreated, pending: pi.status != PeerPushPaymentInitiationStatus.Done,
timestamp: pi.timestampCreated, timestamp: pi.timestampCreated,
talerUri: constructPayPushUri({ talerUri: constructPayPushUri({
exchangeBaseUrl: pi.exchangeBaseUrl, exchangeBaseUrl: pi.exchangeBaseUrl,
@ -478,7 +478,7 @@ function buildTransactionForPullPaymentDebit(
): Transaction { ): Transaction {
return { return {
type: TransactionType.PeerPullDebit, type: TransactionType.PeerPullDebit,
txState: mkTxStateUnknown(), txState: computePeerPullDebitTransactionState(pi),
amountEffective: pi.coinSel?.totalCost amountEffective: pi.coinSel?.totalCost
? pi.coinSel?.totalCost ? pi.coinSel?.totalCost
: Amounts.stringify(pi.contractTerms.amount), : Amounts.stringify(pi.contractTerms.amount),
@ -528,7 +528,7 @@ function buildTransactionForPeerPullCredit(
}); });
return { return {
type: TransactionType.PeerPullCredit, type: TransactionType.PeerPullCredit,
txState: mkTxStateUnknown(), txState: computePeerPullCreditTransactionState(pullCredit),
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.instructedAmount), amountRaw: Amounts.stringify(wsr.instructedAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl, exchangeBaseUrl: wsr.exchangeBaseUrl,
@ -563,7 +563,7 @@ function buildTransactionForPeerPullCredit(
return { return {
type: TransactionType.PeerPullCredit, type: TransactionType.PeerPullCredit,
txState: mkTxStateUnknown(), txState: computePeerPullCreditTransactionState(pullCredit),
amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective),
amountRaw: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount),
exchangeBaseUrl: pullCredit.exchangeBaseUrl, exchangeBaseUrl: pullCredit.exchangeBaseUrl,
@ -602,7 +602,7 @@ function buildTransactionForPeerPushCredit(
return { return {
type: TransactionType.PeerPushCredit, type: TransactionType.PeerPushCredit,
txState: mkTxStateUnknown(), txState: computePeerPushCreditTransactionState(pushInc),
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.instructedAmount), amountRaw: Amounts.stringify(wsr.instructedAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl, exchangeBaseUrl: wsr.exchangeBaseUrl,
@ -626,7 +626,7 @@ function buildTransactionForPeerPushCredit(
return { return {
type: TransactionType.PeerPushCredit, type: TransactionType.PeerPushCredit,
txState: mkTxStateUnknown(), txState: computePeerPushCreditTransactionState(pushInc),
// FIXME: This is wrong, needs to consider fees! // FIXME: This is wrong, needs to consider fees!
amountEffective: Amounts.stringify(peerContractTerms.amount), amountEffective: Amounts.stringify(peerContractTerms.amount),
amountRaw: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount),
@ -666,7 +666,7 @@ function buildTransactionForBankIntegratedWithdraw(
bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl, bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl,
reserveIsReady: reserveIsReady:
wgRecord.status === WithdrawalGroupStatus.Finished || wgRecord.status === WithdrawalGroupStatus.Finished ||
wgRecord.status === WithdrawalGroupStatus.Ready, wgRecord.status === WithdrawalGroupStatus.PendingReady,
}, },
exchangeBaseUrl: wgRecord.exchangeBaseUrl, exchangeBaseUrl: wgRecord.exchangeBaseUrl,
extendedStatus: wgRecord.timestampFinish extendedStatus: wgRecord.timestampFinish
@ -713,7 +713,7 @@ function buildTransactionForManualWithdraw(
exchangePaytoUris, exchangePaytoUris,
reserveIsReady: reserveIsReady:
withdrawalGroup.status === WithdrawalGroupStatus.Finished || withdrawalGroup.status === WithdrawalGroupStatus.Finished ||
withdrawalGroup.status === WithdrawalGroupStatus.Ready, withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
}, },
exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
extendedStatus: withdrawalGroup.timestampFinish extendedStatus: withdrawalGroup.timestampFinish
@ -753,7 +753,7 @@ function buildTransactionForRefresh(
).amount; ).amount;
return { return {
type: TransactionType.Refresh, type: TransactionType.Refresh,
txState: mkTxStateUnknown(), txState: computeRefreshTransactionState(refreshGroupRecord),
refreshReason: refreshGroupRecord.reason, refreshReason: refreshGroupRecord.reason,
amountEffective: Amounts.stringify( amountEffective: Amounts.stringify(
Amounts.zeroOfCurrency(refreshGroupRecord.currency), Amounts.zeroOfCurrency(refreshGroupRecord.currency),
@ -1538,7 +1538,7 @@ export async function retryTransaction(
switch (parsedTx.tag) { switch (parsedTx.tag) {
case TransactionType.PeerPullCredit: { case TransactionType.PeerPullCredit: {
const taskId = constructTaskIdentifier({ const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullInitiation, tag: PendingTaskType.PeerPullCredit,
pursePub: parsedTx.pursePub, pursePub: parsedTx.pursePub,
}); });
await resetOperationTimeout(ws, taskId); await resetOperationTimeout(ws, taskId);
@ -1866,7 +1866,7 @@ export async function abortTransaction(
switch (txId.tag) { switch (txId.tag) {
case TransactionType.Payment: { case TransactionType.Payment: {
await abortPay(ws, txId.proposalId); await abortPayMerchant(ws, txId.proposalId);
break; break;
} }
case TransactionType.Withdrawal: { case TransactionType.Withdrawal: {

View File

@ -135,6 +135,7 @@ import {
notifyTransition, notifyTransition,
stopLongpolling, stopLongpolling,
} from "./transactions.js"; } from "./transactions.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
/** /**
* Logger for this file. * Logger for this file.
@ -160,25 +161,25 @@ export async function suspendWithdrawalTransaction(
} }
let newStatus: WithdrawalGroupStatus | undefined = undefined; let newStatus: WithdrawalGroupStatus | undefined = undefined;
switch (wg.status) { switch (wg.status) {
case WithdrawalGroupStatus.Ready: case WithdrawalGroupStatus.PendingReady:
newStatus = WithdrawalGroupStatus.SuspendedReady; newStatus = WithdrawalGroupStatus.SuspendedReady;
break; break;
case WithdrawalGroupStatus.AbortingBank: case WithdrawalGroupStatus.AbortingBank:
newStatus = WithdrawalGroupStatus.SuspendedAbortingBank; newStatus = WithdrawalGroupStatus.SuspendedAbortingBank;
break; break;
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank; newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank;
break; break;
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank; newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank;
break; break;
case WithdrawalGroupStatus.QueryingStatus: case WithdrawalGroupStatus.PendingQueryingStatus:
newStatus = WithdrawalGroupStatus.QueryingStatus; newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus;
break; break;
case WithdrawalGroupStatus.Kyc: case WithdrawalGroupStatus.PendingKyc:
newStatus = WithdrawalGroupStatus.SuspendedKyc; newStatus = WithdrawalGroupStatus.SuspendedKyc;
break; break;
case WithdrawalGroupStatus.Aml: case WithdrawalGroupStatus.PendingAml:
newStatus = WithdrawalGroupStatus.SuspendedAml; newStatus = WithdrawalGroupStatus.SuspendedAml;
break; break;
default: default:
@ -221,25 +222,25 @@ export async function resumeWithdrawalTransaction(
let newStatus: WithdrawalGroupStatus | undefined = undefined; let newStatus: WithdrawalGroupStatus | undefined = undefined;
switch (wg.status) { switch (wg.status) {
case WithdrawalGroupStatus.SuspendedReady: case WithdrawalGroupStatus.SuspendedReady:
newStatus = WithdrawalGroupStatus.Ready; newStatus = WithdrawalGroupStatus.PendingReady;
break; break;
case WithdrawalGroupStatus.SuspendedAbortingBank: case WithdrawalGroupStatus.SuspendedAbortingBank:
newStatus = WithdrawalGroupStatus.AbortingBank; newStatus = WithdrawalGroupStatus.AbortingBank;
break; break;
case WithdrawalGroupStatus.SuspendedWaitConfirmBank: case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
newStatus = WithdrawalGroupStatus.WaitConfirmBank; newStatus = WithdrawalGroupStatus.PendingWaitConfirmBank;
break; break;
case WithdrawalGroupStatus.SuspendedQueryingStatus: case WithdrawalGroupStatus.SuspendedQueryingStatus:
newStatus = WithdrawalGroupStatus.QueryingStatus; newStatus = WithdrawalGroupStatus.PendingQueryingStatus;
break; break;
case WithdrawalGroupStatus.SuspendedRegisteringBank: case WithdrawalGroupStatus.SuspendedRegisteringBank:
newStatus = WithdrawalGroupStatus.RegisteringBank; newStatus = WithdrawalGroupStatus.PendingRegisteringBank;
break; break;
case WithdrawalGroupStatus.SuspendedAml: case WithdrawalGroupStatus.SuspendedAml:
newStatus = WithdrawalGroupStatus.Aml; newStatus = WithdrawalGroupStatus.PendingAml;
break; break;
case WithdrawalGroupStatus.SuspendedKyc: case WithdrawalGroupStatus.SuspendedKyc:
newStatus = WithdrawalGroupStatus.Kyc; newStatus = WithdrawalGroupStatus.PendingKyc;
break; break;
default: default:
logger.warn( logger.warn(
@ -289,21 +290,21 @@ export async function abortWithdrawalTransaction(
} }
let newStatus: WithdrawalGroupStatus | undefined = undefined; let newStatus: WithdrawalGroupStatus | undefined = undefined;
switch (wg.status) { switch (wg.status) {
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
case WithdrawalGroupStatus.AbortingBank: case WithdrawalGroupStatus.AbortingBank:
newStatus = WithdrawalGroupStatus.AbortingBank; newStatus = WithdrawalGroupStatus.AbortingBank;
break; break;
case WithdrawalGroupStatus.Aml: case WithdrawalGroupStatus.PendingAml:
newStatus = WithdrawalGroupStatus.SuspendedAml; newStatus = WithdrawalGroupStatus.SuspendedAml;
break; break;
case WithdrawalGroupStatus.Kyc: case WithdrawalGroupStatus.PendingKyc:
newStatus = WithdrawalGroupStatus.SuspendedKyc; newStatus = WithdrawalGroupStatus.SuspendedKyc;
break; break;
case WithdrawalGroupStatus.QueryingStatus: case WithdrawalGroupStatus.PendingQueryingStatus:
newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus; newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus;
break; break;
case WithdrawalGroupStatus.Ready: case WithdrawalGroupStatus.PendingReady:
newStatus = WithdrawalGroupStatus.SuspendedReady; newStatus = WithdrawalGroupStatus.SuspendedReady;
break; break;
case WithdrawalGroupStatus.SuspendedAbortingBank: case WithdrawalGroupStatus.SuspendedAbortingBank:
@ -316,9 +317,13 @@ export async function abortWithdrawalTransaction(
case WithdrawalGroupStatus.SuspendedRegisteringBank: case WithdrawalGroupStatus.SuspendedRegisteringBank:
case WithdrawalGroupStatus.SuspendedWaitConfirmBank: case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
case WithdrawalGroupStatus.Finished: case WithdrawalGroupStatus.Finished:
case WithdrawalGroupStatus.BankAborted: case WithdrawalGroupStatus.FailedBankAborted:
case WithdrawalGroupStatus.AbortedExchange:
case WithdrawalGroupStatus.FailedAbortingBank:
// Not allowed // Not allowed
break; break;
default:
assertUnreachable(wg.status);
} }
if (newStatus != null) { if (newStatus != null) {
const oldTxState = computeWithdrawalTransactionStatus(wg); const oldTxState = computeWithdrawalTransactionStatus(wg);
@ -385,7 +390,7 @@ export function computeWithdrawalTransactionStatus(
wgRecord: WithdrawalGroupRecord, wgRecord: WithdrawalGroupRecord,
): TransactionState { ): TransactionState {
switch (wgRecord.status) { switch (wgRecord.status) {
case WithdrawalGroupStatus.BankAborted: case WithdrawalGroupStatus.FailedBankAborted:
return { return {
major: TransactionMajorState.Aborted, major: TransactionMajorState.Aborted,
}; };
@ -393,22 +398,22 @@ export function computeWithdrawalTransactionStatus(
return { return {
major: TransactionMajorState.Done, major: TransactionMajorState.Done,
}; };
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.BankRegisterReserve, minor: TransactionMinorState.BankRegisterReserve,
}; };
case WithdrawalGroupStatus.Ready: case WithdrawalGroupStatus.PendingReady:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.WithdrawCoins, minor: TransactionMinorState.WithdrawCoins,
}; };
case WithdrawalGroupStatus.QueryingStatus: case WithdrawalGroupStatus.PendingQueryingStatus:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.ExchangeWaitReserve, minor: TransactionMinorState.ExchangeWaitReserve,
}; };
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.BankConfirmTransfer, minor: TransactionMinorState.BankConfirmTransfer,
@ -444,13 +449,13 @@ export function computeWithdrawalTransactionStatus(
minor: TransactionMinorState.WithdrawCoins, minor: TransactionMinorState.WithdrawCoins,
}; };
} }
case WithdrawalGroupStatus.Aml: { case WithdrawalGroupStatus.PendingAml: {
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.AmlRequired, minor: TransactionMinorState.AmlRequired,
}; };
} }
case WithdrawalGroupStatus.Kyc: { case WithdrawalGroupStatus.PendingKyc: {
return { return {
major: TransactionMajorState.Pending, major: TransactionMajorState.Pending,
minor: TransactionMinorState.KycRequired, minor: TransactionMinorState.KycRequired,
@ -473,6 +478,11 @@ export function computeWithdrawalTransactionStatus(
major: TransactionMajorState.Failed, major: TransactionMajorState.Failed,
minor: TransactionMinorState.AbortingBank, minor: TransactionMinorState.AbortingBank,
}; };
case WithdrawalGroupStatus.AbortedExchange:
return {
major: TransactionMajorState.Aborted,
minor: TransactionMinorState.Exchange,
}
} }
} }
@ -1122,7 +1132,7 @@ async function queryReserve(
withdrawalGroupId, withdrawalGroupId,
}); });
checkDbInvariant(!!withdrawalGroup); checkDbInvariant(!!withdrawalGroup);
if (withdrawalGroup.status !== WithdrawalGroupStatus.QueryingStatus) { if (withdrawalGroup.status !== WithdrawalGroupStatus.PendingQueryingStatus) {
return { ready: true }; return { ready: true };
} }
const reservePub = withdrawalGroup.reservePub; const reservePub = withdrawalGroup.reservePub;
@ -1135,7 +1145,7 @@ async function queryReserve(
logger.info(`querying reserve status via ${reserveUrl.href}`); logger.info(`querying reserve status via ${reserveUrl.href}`);
const resp = await ws.http.get(reserveUrl.href, { const resp = await ws.http.fetch(reserveUrl.href, {
timeout: getReserveRequestTimeout(withdrawalGroup), timeout: getReserveRequestTimeout(withdrawalGroup),
cancellationToken, cancellationToken,
}); });
@ -1177,7 +1187,7 @@ async function queryReserve(
return undefined; return undefined;
} }
const txStateOld = computeWithdrawalTransactionStatus(wg); const txStateOld = computeWithdrawalTransactionStatus(wg);
wg.status = WithdrawalGroupStatus.Ready; wg.status = WithdrawalGroupStatus.PendingReady;
const txStateNew = computeWithdrawalTransactionStatus(wg); const txStateNew = computeWithdrawalTransactionStatus(wg);
wg.reserveBalanceAmount = Amounts.stringify(result.response.balance); wg.reserveBalanceAmount = Amounts.stringify(result.response.balance);
await tx.withdrawalGroups.put(wg); await tx.withdrawalGroups.put(wg);
@ -1250,12 +1260,12 @@ export async function processWithdrawalGroup(
} }
switch (withdrawalGroup.status) { switch (withdrawalGroup.status) {
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
await processReserveBankStatus(ws, withdrawalGroupId); await processReserveBankStatus(ws, withdrawalGroupId);
return await processWithdrawalGroup(ws, withdrawalGroupId, { return await processWithdrawalGroup(ws, withdrawalGroupId, {
forceNow: true, forceNow: true,
}); });
case WithdrawalGroupStatus.QueryingStatus: { case WithdrawalGroupStatus.PendingQueryingStatus: {
runLongpollAsync(ws, retryTag, (ct) => { runLongpollAsync(ws, retryTag, (ct) => {
return queryReserve(ws, withdrawalGroupId, ct); return queryReserve(ws, withdrawalGroupId, ct);
}); });
@ -1266,7 +1276,7 @@ export async function processWithdrawalGroup(
type: OperationAttemptResultType.Longpoll, type: OperationAttemptResultType.Longpoll,
}; };
} }
case WithdrawalGroupStatus.WaitConfirmBank: { case WithdrawalGroupStatus.PendingWaitConfirmBank: {
const res = await processReserveBankStatus(ws, withdrawalGroupId); const res = await processReserveBankStatus(ws, withdrawalGroupId);
switch (res.status) { switch (res.status) {
case BankStatusResultCode.Aborted: case BankStatusResultCode.Aborted:
@ -1284,7 +1294,7 @@ export async function processWithdrawalGroup(
} }
break; break;
} }
case WithdrawalGroupStatus.BankAborted: { case WithdrawalGroupStatus.FailedBankAborted: {
// FIXME // FIXME
return { return {
type: OperationAttemptResultType.Pending, type: OperationAttemptResultType.Pending,
@ -1294,7 +1304,7 @@ export async function processWithdrawalGroup(
case WithdrawalGroupStatus.Finished: case WithdrawalGroupStatus.Finished:
// We can try to withdraw, nothing needs to be done with the reserve. // We can try to withdraw, nothing needs to be done with the reserve.
break; break;
case WithdrawalGroupStatus.Ready: case WithdrawalGroupStatus.PendingReady:
// Continue with the actual withdrawal! // Continue with the actual withdrawal!
break; break;
default: default:
@ -1847,8 +1857,8 @@ async function registerReserveWithBank(
withdrawalGroupId, withdrawalGroupId,
}); });
switch (withdrawalGroup?.status) { switch (withdrawalGroup?.status) {
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
break; break;
default: default:
return; return;
@ -1885,8 +1895,8 @@ async function registerReserveWithBank(
return undefined; return undefined;
} }
switch (r.status) { switch (r.status) {
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
break; break;
default: default:
return; return;
@ -1898,7 +1908,7 @@ async function registerReserveWithBank(
AbsoluteTime.now(), AbsoluteTime.now(),
); );
const oldTxState = computeWithdrawalTransactionStatus(r); const oldTxState = computeWithdrawalTransactionStatus(r);
r.status = WithdrawalGroupStatus.WaitConfirmBank; r.status = WithdrawalGroupStatus.PendingWaitConfirmBank;
const newTxState = computeWithdrawalTransactionStatus(r); const newTxState = computeWithdrawalTransactionStatus(r);
await tx.withdrawalGroups.put(r); await tx.withdrawalGroups.put(r);
return { return {
@ -1928,8 +1938,8 @@ async function processReserveBankStatus(
withdrawalGroupId, withdrawalGroupId,
}); });
switch (withdrawalGroup?.status) { switch (withdrawalGroup?.status) {
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
break; break;
default: default:
return { return {
@ -1969,8 +1979,8 @@ async function processReserveBankStatus(
return; return;
} }
switch (r.status) { switch (r.status) {
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
break; break;
default: default:
return; return;
@ -1981,7 +1991,7 @@ async function processReserveBankStatus(
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
const oldTxState = computeWithdrawalTransactionStatus(r); const oldTxState = computeWithdrawalTransactionStatus(r);
r.wgInfo.bankInfo.timestampBankConfirmed = now; r.wgInfo.bankInfo.timestampBankConfirmed = now;
r.status = WithdrawalGroupStatus.BankAborted; r.status = WithdrawalGroupStatus.FailedBankAborted;
const newTxState = computeWithdrawalTransactionStatus(r); const newTxState = computeWithdrawalTransactionStatus(r);
await tx.withdrawalGroups.put(r); await tx.withdrawalGroups.put(r);
return { return {
@ -2002,7 +2012,7 @@ async function processReserveBankStatus(
} }
// FIXME: Why do we do this?! // FIXME: Why do we do this?!
if (withdrawalGroup.status === WithdrawalGroupStatus.RegisteringBank) { if (withdrawalGroup.status === WithdrawalGroupStatus.PendingRegisteringBank) {
await registerReserveWithBank(ws, withdrawalGroupId); await registerReserveWithBank(ws, withdrawalGroupId);
return await processReserveBankStatus(ws, withdrawalGroupId); return await processReserveBankStatus(ws, withdrawalGroupId);
} }
@ -2016,8 +2026,8 @@ async function processReserveBankStatus(
} }
// Re-check reserve status within transaction // Re-check reserve status within transaction
switch (r.status) { switch (r.status) {
case WithdrawalGroupStatus.RegisteringBank: case WithdrawalGroupStatus.PendingRegisteringBank:
case WithdrawalGroupStatus.WaitConfirmBank: case WithdrawalGroupStatus.PendingWaitConfirmBank:
break; break;
default: default:
return undefined; return undefined;
@ -2030,7 +2040,7 @@ async function processReserveBankStatus(
logger.info("withdrawal: transfer confirmed by bank."); logger.info("withdrawal: transfer confirmed by bank.");
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
r.wgInfo.bankInfo.timestampBankConfirmed = now; r.wgInfo.bankInfo.timestampBankConfirmed = now;
r.status = WithdrawalGroupStatus.QueryingStatus; r.status = WithdrawalGroupStatus.PendingQueryingStatus;
// FIXME: Notification is deprecated with DD37. // FIXME: Notification is deprecated with DD37.
ws.notify({ ws.notify({
type: NotificationType.WithdrawalGroupBankConfirmed, type: NotificationType.WithdrawalGroupBankConfirmed,
@ -2276,7 +2286,7 @@ export async function acceptWithdrawalFromUri(
}, },
restrictAge: req.restrictAge, restrictAge: req.restrictAge,
forcedDenomSel: req.forcedDenomSel, forcedDenomSel: req.forcedDenomSel,
reserveStatus: WithdrawalGroupStatus.RegisteringBank, reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank,
}); });
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
@ -2291,19 +2301,14 @@ export async function acceptWithdrawalFromUri(
const processedWithdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { const processedWithdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
withdrawalGroupId, withdrawalGroupId,
}); });
if (processedWithdrawalGroup?.status === WithdrawalGroupStatus.BankAborted) { if (processedWithdrawalGroup?.status === WithdrawalGroupStatus.FailedBankAborted) {
throw TalerError.fromDetail( throw TalerError.fromDetail(
TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
{}, {},
); );
} }
// Start withdrawal in the background ws.workAvailable.trigger();
processWithdrawalGroup(ws, withdrawalGroupId, {
forceNow: true,
}).catch((err) => {
logger.error("Processing withdrawal (after creation) failed:", err);
});
return { return {
reservePub: withdrawalGroup.reservePub, reservePub: withdrawalGroup.reservePub,
@ -2337,7 +2342,7 @@ export async function createManualWithdrawal(
exchangeBaseUrl: req.exchangeBaseUrl, exchangeBaseUrl: req.exchangeBaseUrl,
forcedDenomSel: req.forcedDenomSel, forcedDenomSel: req.forcedDenomSel,
restrictAge: req.restrictAge, restrictAge: req.restrictAge,
reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
}); });
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
@ -2357,18 +2362,7 @@ export async function createManualWithdrawal(
return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId);
}); });
// Start withdrawal in the background (do not await!) ws.workAvailable.trigger();
// FIXME: We could also interrupt the task look if it is waiting and
// rely on retry handling to re-process the withdrawal group.
runOperationWithErrorReporting(
ws,
TaskIdentifiers.forWithdrawal(withdrawalGroup),
async () => {
return await processWithdrawalGroup(ws, withdrawalGroupId, {
forceNow: true,
});
},
);
return { return {
reservePub: withdrawalGroup.reservePub, reservePub: withdrawalGroup.reservePub,

View File

@ -37,9 +37,8 @@ export enum PendingTaskType {
Withdraw = "withdraw", Withdraw = "withdraw",
Deposit = "deposit", Deposit = "deposit",
Backup = "backup", Backup = "backup",
// FIXME: Rename to peer-push-debit and peer-pull-debit PeerPushDebit = "peer-push-debit",
PeerPushInitiation = "peer-push-initiation", PeerPullCredit = "peer-pull-credit",
PeerPullInitiation = "peer-pull-initiation",
PeerPushCredit = "peer-push-credit", PeerPushCredit = "peer-push-credit",
PeerPullDebit = "peer-pull-debit", PeerPullDebit = "peer-pull-debit",
} }
@ -83,7 +82,7 @@ export interface PendingExchangeUpdateTask {
* The wallet wants to send a peer push payment. * The wallet wants to send a peer push payment.
*/ */
export interface PendingPeerPushInitiationTask { export interface PendingPeerPushInitiationTask {
type: PendingTaskType.PeerPushInitiation; type: PendingTaskType.PeerPushDebit;
pursePub: string; pursePub: string;
} }
@ -91,7 +90,7 @@ export interface PendingPeerPushInitiationTask {
* The wallet wants to send a peer pull payment. * The wallet wants to send a peer pull payment.
*/ */
export interface PendingPeerPullInitiationTask { export interface PendingPeerPullInitiationTask {
type: PendingTaskType.PeerPullInitiation; type: PendingTaskType.PeerPullCredit;
pursePub: string; pursePub: string;
} }

View File

@ -197,9 +197,9 @@ export type ParsedTaskIdentifier =
| { tag: PendingTaskType.ExchangeCheckRefresh; exchangeBaseUrl: string } | { tag: PendingTaskType.ExchangeCheckRefresh; exchangeBaseUrl: string }
| { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string } | { tag: PendingTaskType.ExchangeUpdate; exchangeBaseUrl: string }
| { tag: PendingTaskType.PeerPullDebit; peerPullPaymentIncomingId: string } | { tag: PendingTaskType.PeerPullDebit; peerPullPaymentIncomingId: string }
| { tag: PendingTaskType.PeerPullInitiation; pursePub: string } | { tag: PendingTaskType.PeerPullCredit; pursePub: string }
| { tag: PendingTaskType.PeerPushCredit; peerPushPaymentIncomingId: string } | { tag: PendingTaskType.PeerPushCredit; peerPushPaymentIncomingId: string }
| { tag: PendingTaskType.PeerPushInitiation; pursePub: string } | { tag: PendingTaskType.PeerPushDebit; pursePub: string }
| { tag: PendingTaskType.Purchase; proposalId: string } | { tag: PendingTaskType.Purchase; proposalId: string }
| { tag: PendingTaskType.Recoup; recoupGroupId: string } | { tag: PendingTaskType.Recoup; recoupGroupId: string }
| { tag: PendingTaskType.TipPickup; walletTipId: string } | { tag: PendingTaskType.TipPickup; walletTipId: string }
@ -223,9 +223,9 @@ export function constructTaskIdentifier(p: ParsedTaskIdentifier): string {
return `${p.tag}:${p.peerPullPaymentIncomingId}`; return `${p.tag}:${p.peerPullPaymentIncomingId}`;
case PendingTaskType.PeerPushCredit: case PendingTaskType.PeerPushCredit:
return `${p.tag}:${p.peerPushPaymentIncomingId}`; return `${p.tag}:${p.peerPushPaymentIncomingId}`;
case PendingTaskType.PeerPullInitiation: case PendingTaskType.PeerPullCredit:
return `${p.tag}:${p.pursePub}`; return `${p.tag}:${p.pursePub}`;
case PendingTaskType.PeerPushInitiation: case PendingTaskType.PeerPushDebit:
return `${p.tag}:${p.pursePub}`; return `${p.tag}:${p.pursePub}`;
case PendingTaskType.Purchase: case PendingTaskType.Purchase:
return `${p.tag}:${p.proposalId}`; return `${p.tag}:${p.proposalId}`;
@ -276,12 +276,12 @@ export namespace TaskIdentifiers {
export function forPeerPushPaymentInitiation( export function forPeerPushPaymentInitiation(
ppi: PeerPushPaymentInitiationRecord, ppi: PeerPushPaymentInitiationRecord,
): string { ): string {
return `${PendingTaskType.PeerPushInitiation}:${ppi.pursePub}`; return `${PendingTaskType.PeerPushDebit}:${ppi.pursePub}`;
} }
export function forPeerPullPaymentInitiation( export function forPeerPullPaymentInitiation(
ppi: PeerPullPaymentInitiationRecord, ppi: PeerPullPaymentInitiationRecord,
): string { ): string {
return `${PendingTaskType.PeerPullInitiation}:${ppi.pursePub}`; return `${PendingTaskType.PeerPullCredit}:${ppi.pursePub}`;
} }
export function forPeerPullPaymentDebit( export function forPeerPullPaymentDebit(
ppi: PeerPullPaymentIncomingRecord, ppi: PeerPullPaymentIncomingRecord,

View File

@ -208,7 +208,7 @@ import {
processPeerPullCredit, processPeerPullCredit,
processPeerPullDebit, processPeerPullDebit,
processPeerPushCredit, processPeerPushCredit,
processPeerPushInitiation, processPeerPushDebit,
} from "./operations/pay-peer.js"; } from "./operations/pay-peer.js";
import { getPendingOperations } from "./operations/pending.js"; import { getPendingOperations } from "./operations/pending.js";
import { import {
@ -314,9 +314,9 @@ async function callOperationHandler(
} }
case PendingTaskType.Backup: case PendingTaskType.Backup:
return await processBackupForProvider(ws, pending.backupProviderBaseUrl); return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
case PendingTaskType.PeerPushInitiation: case PendingTaskType.PeerPushDebit:
return await processPeerPushInitiation(ws, pending.pursePub); return await processPeerPushDebit(ws, pending.pursePub);
case PendingTaskType.PeerPullInitiation: case PendingTaskType.PeerPullCredit:
return await processPeerPullCredit(ws, pending.pursePub); return await processPeerPullCredit(ws, pending.pursePub);
case PendingTaskType.PeerPullDebit: case PendingTaskType.PeerPullDebit:
return await processPeerPullDebit(ws, pending.peerPullPaymentIncomingId); return await processPeerPullDebit(ws, pending.peerPullPaymentIncomingId);
@ -439,7 +439,7 @@ async function runTaskLoop(
}); });
// Wait until either the timeout, or we are notified (via the latch) // Wait until either the timeout, or we are notified (via the latch)
// that more work might be available. // that more work might be available.
await Promise.race([timeout, ws.latch.wait()]); await Promise.race([timeout, ws.workAvailable.wait()]);
} else { } else {
logger.trace( logger.trace(
`running ${pending.pendingOperations.length} pending operations`, `running ${pending.pendingOperations.length} pending operations`,
@ -1659,7 +1659,7 @@ class InternalWalletStateImpl implements InternalWalletState {
merchantInfoCache: Record<string, MerchantInfo> = {}; merchantInfoCache: Record<string, MerchantInfo> = {};
readonly timerGroup: TimerGroup; readonly timerGroup: TimerGroup;
latch = new AsyncCondition(); workAvailable = new AsyncCondition();
stopped = false; stopped = false;
listeners: NotificationListener[] = []; listeners: NotificationListener[] = [];