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

View File

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

View File

@ -130,10 +130,10 @@ export enum OperationStatusRange {
// Operations that need to be actively processed.
ACTIVE_START = 10,
ACTIVE_END = 29,
// Operations that need user input, but nothing can be done
// automatically.
USER_ATTENTION_START = 30,
USER_ATTENTION_END = 49,
// Operations that are suspended and might
// expire, but nothing else can be done.
SUSPENDED_START = 30,
SUSPENDED_END = 49,
// Operations that don't need any attention or processing.
DORMANT_START = 50,
DORMANT_END = 69,
@ -146,24 +146,24 @@ export enum WithdrawalGroupStatus {
/**
* Reserve must be registered with the bank.
*/
RegisteringBank = 10,
PendingRegisteringBank = 10,
/**
* We've registered reserve's information with the bank
* and are now waiting for the user to confirm the withdraw
* with the bank (typically 2nd factor auth).
*/
WaitConfirmBank = 11,
PendingWaitConfirmBank = 11,
/**
* Querying reserve status with the exchange.
*/
QueryingStatus = 12,
PendingQueryingStatus = 12,
/**
* Ready for withdrawal.
*/
Ready = 13,
PendingReady = 13,
/**
* 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.
*/
Kyc = 16,
PendingKyc = 16,
/**
* 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.
@ -191,16 +199,16 @@ export enum WithdrawalGroupStatus {
/**
* 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,
/**
* 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 = 10 /* ACTIVE_START */,
PurseCreated = 50 /* DORMANT_START */,
PendingCreatePurse = 10 /* ACTIVE_START */,
PendingReady = 11,
AbortingDeletePurse = 12,
AbortingRefresh = 13,
SuspendedCreatePurse = 30,
SuspendedReady = 31,
SuspendedAbortingDeletePurse = 32,
SuspendedAbortingRefresh = 33,
Done = 50 /* DORMANT_START */,
Aborted = 51,
}
export interface PeerPushPaymentCoinSelection {
@ -1856,14 +1874,18 @@ export interface PeerPushPaymentInitiationRecord {
}
export enum PeerPullPaymentInitiationStatus {
Initial = 10 /* ACTIVE_START */,
PendingCreatePurse = 10 /* ACTIVE_START */,
/**
* Purse created, waiting for the other party to accept the
* invoice and deposit money into it.
*/
PurseCreated = 11 /* ACTIVE_START + 1 */,
KycRequired = 12 /* ACTIVE_START + 2 */,
PurseDeposited = 50 /* DORMANT_START */,
PendingReady = 11 /* ACTIVE_START + 1 */,
PendingMergeKycRequired = 12 /* ACTIVE_START + 2 */,
PendingWithdrawing = 13,
SuspendedCreatePurse = 30,
SuspendedReady = 31,
SuspendedWithdrawing = 32,
DonePurseDeposited = 50 /* DORMANT_START */,
}
export interface PeerPullPaymentInitiationRecord {
@ -1921,12 +1943,13 @@ export interface PeerPullPaymentInitiationRecord {
export enum PeerPushPaymentIncomingStatus {
Proposed = 30 /* USER_ATTENTION_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
* 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.
*/
latch: AsyncCondition;
workAvailable: AsyncCondition;
listeners: NotificationListener[];

View File

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

View File

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

View File

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

View File

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

View File

@ -75,20 +75,21 @@ import {
NotificationType,
HttpStatusCode,
codecForWalletKycUuid,
WalletKycUuid,
TransactionState,
TransactionMajorState,
TransactionMinorState,
} from "@gnu-taler/taler-util";
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
import {
DenominationRecord,
KycPendingInfo,
KycUserType,
OperationStatus,
PeerPullPaymentIncomingRecord,
PeerPullPaymentIncomingStatus,
PeerPullPaymentInitiationRecord,
PeerPullPaymentInitiationStatus,
PeerPushPaymentCoinSelection,
PeerPushPaymentIncomingRecord,
PeerPushPaymentIncomingStatus,
PeerPushPaymentInitiationRecord,
PeerPushPaymentInitiationStatus,
ReserveRecord,
WithdrawalGroupStatus,
@ -98,7 +99,6 @@ import { TalerError } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
LongpollResult,
makeTransactionId,
resetOperationTimeout,
runLongpollAsync,
runOperationWithErrorReporting,
@ -128,8 +128,10 @@ import {
import { PendingTaskType } from "../pending-types.js";
import {
constructTransactionIdentifier,
notifyTransition,
stopLongpolling,
} from "./transactions.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
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,
pursePub: string,
peerPushInitiation: PeerPushPaymentInitiationRecord,
): 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 pursePub = peerPushInitiation.pursePub;
const purseExpiration = peerPushInitiation.purseExpiration;
const hContractTerms = peerPushInitiation.contractTermsHash;
@ -501,7 +495,9 @@ export async function processPeerPushInitiation(
peerPushInitiation.exchangeBaseUrl,
);
const httpResp = await ws.http.postJson(createPurseUrl.href, {
const httpResp = await ws.http.fetch(createPurseUrl.href, {
method: "POST",
body: {
amount: peerPushInitiation.amount,
merge_pub: peerPushInitiation.mergePub,
purse_sig: purseSigResp.sig,
@ -510,13 +506,14 @@ export async function processPeerPushInitiation(
deposits: depositSigsResp.deposits,
min_age: 0,
econtract: econtractResp.econtract,
},
});
const resp = await httpResp.json();
logger.info(`resp: ${j2s(resp)}`);
if (httpResp.status !== 200) {
if (httpResp.status !== HttpStatusCode.Ok) {
throw Error("got error response from exchange");
}
@ -527,7 +524,7 @@ export async function processPeerPushInitiation(
if (!ppi) {
return;
}
ppi.status = PeerPushPaymentInitiationStatus.PurseCreated;
ppi.status = PeerPushPaymentInitiationStatus.Done;
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.
*/
@ -612,7 +725,7 @@ export async function initiatePeerPushPayment(
pursePriv: pursePair.priv,
pursePub: pursePair.pub,
timestampCreated: TalerProtocolTimestamp.now(),
status: PeerPushPaymentInitiationStatus.Initiated,
status: PeerPushPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
coinSel: {
coinPubs: sel.coins.map((x) => x.coinPub),
@ -628,12 +741,12 @@ export async function initiatePeerPushPayment(
});
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushInitiation,
tag: PendingTaskType.PeerPushDebit,
pursePub: pursePair.pub,
});
await runOperationWithErrorReporting(ws, taskId, async () => {
return await processPeerPushInitiation(ws, pursePair.pub);
return await processPeerPushDebit(ws, pursePair.pub);
});
return {
@ -645,22 +758,24 @@ export async function initiatePeerPushPayment(
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
contractPriv: contractKeyPair.priv,
}),
transactionId: makeTransactionId(
TransactionType.PeerPushDebit,
pursePair.pub,
),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub: pursePair.pub,
}),
};
}
interface ExchangePurseStatus {
balance: AmountString;
deposit_timestamp?: TalerProtocolTimestamp;
merge_timestamp?: TalerProtocolTimestamp;
}
export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
buildCodecForObject<ExchangePurseStatus>()
.property("balance", codecForAmountString())
.property("deposit_timestamp", codecOptional(codecForTimestamp))
.property("merge_timestamp", codecOptional(codecForTimestamp))
.build("ExchangePurseStatus");
export async function preparePeerPushCredit(
@ -879,13 +994,13 @@ export async function processPeerPushCredit(
const amount = Amounts.parseOrThrow(contractTerms.amount);
if (
peerInc.status === PeerPushPaymentIncomingStatus.KycRequired &&
peerInc.status === PeerPushPaymentIncomingStatus.MergeKycRequired &&
peerInc.kycInfo
) {
const txId = makeTransactionId(
TransactionType.PeerPushCredit,
peerInc.peerPushPaymentIncomingId,
);
const txId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId,
});
await checkWithdrawalKycStatus(
ws,
peerInc.exchangeBaseUrl,
@ -951,7 +1066,7 @@ export async function processPeerPushCredit(
paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row,
};
peerInc.status = PeerPushPaymentIncomingStatus.KycRequired;
peerInc.status = PeerPushPaymentIncomingStatus.MergeKycRequired;
await tx.peerPushPaymentIncoming.put(peerInc);
});
return {
@ -975,7 +1090,7 @@ export async function processPeerPushCredit(
},
forcedWithdrawalGroupId: peerInc.withdrawalGroupId,
exchangeBaseUrl: peerInc.exchangeBaseUrl,
reserveStatus: WithdrawalGroupStatus.QueryingStatus,
reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: {
priv: mergeReserveInfo.reservePriv,
pub: mergeReserveInfo.reservePub,
@ -993,9 +1108,9 @@ export async function processPeerPushCredit(
}
if (
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);
});
@ -1011,7 +1126,7 @@ export async function confirmPeerPushCredit(
req: ConfirmPeerPushCreditRequest,
): Promise<AcceptPeerPushPaymentResponse> {
let peerInc: PeerPushPaymentIncomingRecord | undefined;
let contractTerms: PeerContractTerms | undefined;
await ws.db
.mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
@ -1021,10 +1136,6 @@ export async function confirmPeerPushCredit(
if (!peerInc) {
return;
}
const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash);
if (ctRec) {
contractTerms = ctRec.contractTermsRaw;
}
if (peerInc.status === PeerPushPaymentIncomingStatus.Proposed) {
peerInc.status = PeerPushPaymentIncomingStatus.Accepted;
}
@ -1037,21 +1148,15 @@ export async function confirmPeerPushCredit(
);
}
checkDbInvariant(!!contractTerms);
ws.workAvailable.trigger();
await updateExchangeFromUrl(ws, peerInc.exchangeBaseUrl);
const retryTag = TaskIdentifiers.forPeerPushCredit(peerInc);
await runOperationWithErrorReporting(ws, retryTag, () =>
processPeerPushCredit(ws, req.peerPushPaymentIncomingId),
);
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId: req.peerPushPaymentIncomingId,
});
return {
transactionId: makeTransactionId(
TransactionType.PeerPushCredit,
req.peerPushPaymentIncomingId,
),
transactionId,
};
}
@ -1209,11 +1314,13 @@ export async function confirmPeerPullDebit(
},
);
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId: req.peerPullPaymentIncomingId,
});
return {
transactionId: makeTransactionId(
TransactionType.PeerPullDebit,
req.peerPullPaymentIncomingId,
),
transactionId,
};
}
@ -1395,7 +1502,7 @@ export async function queryPurseForPeerPullCredit(
},
forcedWithdrawalGroupId: pullIni.withdrawalGroupId,
exchangeBaseUrl: pullIni.exchangeBaseUrl,
reserveStatus: WithdrawalGroupStatus.QueryingStatus,
reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus,
reserveKeyPair: {
priv: reserve.reservePriv,
pub: reserve.reservePub,
@ -1410,8 +1517,8 @@ export async function queryPurseForPeerPullCredit(
logger.warn("peerPullPaymentInitiation not found anymore");
return;
}
if (finPi.status === PeerPullPaymentInitiationStatus.PurseCreated) {
finPi.status = PeerPullPaymentInitiationStatus.PurseDeposited;
if (finPi.status === PeerPullPaymentInitiationStatus.PendingReady) {
finPi.status = PeerPullPaymentInitiationStatus.DonePurseDeposited;
}
await tx.peerPullPaymentInitiations.put(finPi);
});
@ -1434,7 +1541,7 @@ export async function processPeerPullCredit(
}
const retryTag = constructTaskIdentifier({
tag: PendingTaskType.PeerPullInitiation,
tag: PendingTaskType.PeerPullCredit,
pursePub,
});
@ -1449,7 +1556,7 @@ export async function processPeerPullCredit(
logger.trace(`processing ${retryTag}, status=${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
// also retries the withdrawal task.
@ -1475,7 +1582,7 @@ export async function processPeerPullCredit(
result: undefined,
};
}
case PeerPullPaymentInitiationStatus.PurseCreated:
case PeerPullPaymentInitiationStatus.PendingReady:
runLongpollAsync(ws, retryTag, async (cancellationToken) =>
queryPurseForPeerPullCredit(ws, pullIni, cancellationToken),
);
@ -1485,23 +1592,23 @@ export async function processPeerPullCredit(
return {
type: OperationAttemptResultType.Longpoll,
};
case PeerPullPaymentInitiationStatus.KycRequired: {
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub: pullIni.pursePub,
});
if (pullIni.kycInfo) {
const txId = makeTransactionId(
TransactionType.PeerPullCredit,
pullIni.pursePub,
);
await checkWithdrawalKycStatus(
ws,
pullIni.exchangeBaseUrl,
txId,
transactionId,
pullIni.kycInfo,
"individual",
);
}
break;
}
case PeerPullPaymentInitiationStatus.Initial:
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
break;
default:
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
@ -1590,7 +1697,8 @@ export async function processPeerPullCredit(
paytoHash: kycPending.h_payto,
requirementRow: kycPending.requirement_row,
};
peerIni.status = PeerPullPaymentInitiationStatus.KycRequired;
peerIni.status =
PeerPullPaymentInitiationStatus.PendingMergeKycRequired;
await tx.peerPullPaymentInitiations.put(peerIni);
});
return {
@ -1610,7 +1718,7 @@ export async function processPeerPullCredit(
if (!pi2) {
return;
}
pi2.status = PeerPullPaymentInitiationStatus.PurseCreated;
pi2.status = PeerPullPaymentInitiationStatus.PendingReady;
await tx.peerPullPaymentInitiations.put(pi2);
});
@ -1776,7 +1884,7 @@ export async function initiatePeerPullPayment(
pursePub: pursePair.pub,
mergePriv: mergePair.priv,
mergePub: mergePair.pub,
status: PeerPullPaymentInitiationStatus.Initial,
status: PeerPullPaymentInitiationStatus.PendingCreatePurse,
contractTerms: contractTerms,
mergeTimestamp,
mergeReserveRowId: mergeReserveRowId,
@ -1796,7 +1904,7 @@ export async function initiatePeerPullPayment(
// check this asynchronously from the transaction status?
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullInitiation,
tag: PendingTaskType.PeerPullCredit,
pursePub: pursePair.pub,
});
@ -1804,14 +1912,408 @@ export async function initiatePeerPullPayment(
return processPeerPullCredit(ws, pursePair.pub);
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub: pursePair.pub,
});
return {
talerUri: constructPayPullUri({
exchangeBaseUrl: exchangeBaseUrl,
contractPriv: contractKeyPair.priv,
}),
transactionId: makeTransactionId(
TransactionType.PeerPullCredit,
pursePair.pub,
),
transactionId,
};
}
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,
): Promise<void> {
await tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => {
if (pi.status === PeerPullPaymentInitiationStatus.PurseDeposited) {
if (pi.status === PeerPullPaymentInitiationStatus.DonePurseDeposited) {
return;
}
const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPullInitiation,
type: PendingTaskType.PeerPullCredit,
...getPendingCommon(ws, opId, timestampDue),
givesLifeness: true,
retryInfo: retryRecord?.retryInfo,
@ -421,14 +421,14 @@ async function gatherPeerPushInitiationPending(
resp: PendingOperationsResponse,
): Promise<void> {
await tx.peerPushPaymentInitiations.iter().forEachAsync(async (pi) => {
if (pi.status === PeerPushPaymentInitiationStatus.PurseCreated) {
if (pi.status === PeerPushPaymentInitiationStatus.Done) {
return;
}
const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPushInitiation,
type: PendingTaskType.PeerPushDebit,
...getPendingCommon(ws, opId, timestampDue),
givesLifeness: true,
retryInfo: retryRecord?.retryInfo,
@ -450,7 +450,7 @@ async function gatherPeerPushCreditPending(
switch (pi.status) {
case PeerPushPaymentIncomingStatus.Proposed:
return;
case PeerPushPaymentIncomingStatus.WithdrawalCreated:
case PeerPushPaymentIncomingStatus.Done:
return;
}
const opId = TaskIdentifiers.forPeerPushCredit(pi);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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