-withdrawal notifications
This commit is contained in:
parent
c4f5c83b8e
commit
16d30adf0d
@ -1680,21 +1680,15 @@ export const codecForResumeTransaction = (): Codec<ResumeTransactionRequest> =>
|
|||||||
|
|
||||||
export interface AbortTransactionRequest {
|
export interface AbortTransactionRequest {
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
export interface CancelAbortingTransactionRequest {
|
||||||
* Move the payment immediately into an aborted state.
|
transactionId: string;
|
||||||
* The UI should warn the user that this might lead
|
|
||||||
* to money being lost.
|
|
||||||
*
|
|
||||||
* Defaults to false.
|
|
||||||
*/
|
|
||||||
forceImmediateAbort?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> =>
|
export const codecForAbortTransaction = (): Codec<AbortTransactionRequest> =>
|
||||||
buildCodecForObject<AbortTransactionRequest>()
|
buildCodecForObject<AbortTransactionRequest>()
|
||||||
.property("transactionId", codecForString())
|
.property("transactionId", codecForString())
|
||||||
.property("forceImmediateAbort", codecOptional(codecForBoolean()))
|
|
||||||
.build("AbortTransactionRequest");
|
.build("AbortTransactionRequest");
|
||||||
|
|
||||||
export interface DepositGroupFees {
|
export interface DepositGroupFees {
|
||||||
|
@ -443,6 +443,21 @@ transactionsCli
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
transactionsCli
|
||||||
|
.subcommand("cancelAbortingTransaction", "suspend", {
|
||||||
|
help: "Cancel the attempt of properly aborting a transaction.",
|
||||||
|
})
|
||||||
|
.requiredArgument("transactionId", clk.STRING, {
|
||||||
|
help: "Identifier of the transaction to cancel aborting.",
|
||||||
|
})
|
||||||
|
.action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
await wallet.client.call(WalletApiOperation.CancelAbortingTransaction, {
|
||||||
|
transactionId: args.cancelAbortingTransaction.transactionId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
transactionsCli
|
transactionsCli
|
||||||
.subcommand("resumeTransaction", "resume", {
|
.subcommand("resumeTransaction", "resume", {
|
||||||
help: "Resume a transaction.",
|
help: "Resume a transaction.",
|
||||||
@ -484,14 +499,10 @@ transactionsCli
|
|||||||
.requiredArgument("transactionId", clk.STRING, {
|
.requiredArgument("transactionId", clk.STRING, {
|
||||||
help: "Identifier of the transaction to delete",
|
help: "Identifier of the transaction to delete",
|
||||||
})
|
})
|
||||||
.flag("force", ["--force"], {
|
|
||||||
help: "Force aborting the transaction. Might lose money.",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
.action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
await wallet.client.call(WalletApiOperation.AbortTransaction, {
|
await wallet.client.call(WalletApiOperation.AbortTransaction, {
|
||||||
transactionId: args.abortTransaction.transactionId,
|
transactionId: args.abortTransaction.transactionId,
|
||||||
forceImmediateAbort: args.abortTransaction.force,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
ExtendedStatus,
|
ExtendedStatus,
|
||||||
j2s,
|
j2s,
|
||||||
Logger,
|
Logger,
|
||||||
|
NotificationType,
|
||||||
OrderShortInfo,
|
OrderShortInfo,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
PeerContractTerms,
|
PeerContractTerms,
|
||||||
@ -38,6 +39,7 @@ import {
|
|||||||
TransactionMajorState,
|
TransactionMajorState,
|
||||||
TransactionsRequest,
|
TransactionsRequest,
|
||||||
TransactionsResponse,
|
TransactionsResponse,
|
||||||
|
TransactionState,
|
||||||
TransactionType,
|
TransactionType,
|
||||||
WithdrawalType,
|
WithdrawalType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
@ -94,6 +96,7 @@ import { processPeerPullCredit } from "./pay-peer.js";
|
|||||||
import { processRefreshGroup } from "./refresh.js";
|
import { processRefreshGroup } from "./refresh.js";
|
||||||
import { computeTipTransactionStatus, processTip } from "./tip.js";
|
import { computeTipTransactionStatus, processTip } from "./tip.js";
|
||||||
import {
|
import {
|
||||||
|
abortWithdrawalTransaction,
|
||||||
augmentPaytoUrisForWithdrawal,
|
augmentPaytoUrisForWithdrawal,
|
||||||
computeWithdrawalTransactionStatus,
|
computeWithdrawalTransactionStatus,
|
||||||
processWithdrawalGroup,
|
processWithdrawalGroup,
|
||||||
@ -1854,24 +1857,55 @@ export async function deleteTransaction(
|
|||||||
export async function abortTransaction(
|
export async function abortTransaction(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
transactionId: string,
|
transactionId: string,
|
||||||
forceImmediateAbort?: boolean,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { type, args: rest } = parseId("txn", transactionId);
|
const txId = parseTransactionIdentifier(transactionId);
|
||||||
|
if (!txId) {
|
||||||
|
throw Error("invalid transaction identifier");
|
||||||
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (txId.tag) {
|
||||||
case TransactionType.Payment: {
|
case TransactionType.Payment: {
|
||||||
const proposalId = rest[0];
|
await abortPay(ws, txId.proposalId);
|
||||||
await abortPay(ws, proposalId, forceImmediateAbort);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionType.PeerPushDebit: {
|
case TransactionType.Withdrawal: {
|
||||||
|
await abortWithdrawalTransaction(ws, txId.withdrawalGroupId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
const unknownTxType: any = type;
|
const unknownTxType: any = txId.tag;
|
||||||
throw Error(
|
throw Error(
|
||||||
`can't abort a '${unknownTxType}' transaction: not yet implemented`,
|
`can't abort a '${unknownTxType}' transaction: not yet implemented`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TransitionInfo {
|
||||||
|
oldTxState: TransactionState;
|
||||||
|
newTxState: TransactionState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify of a state transition if necessary.
|
||||||
|
*/
|
||||||
|
export function notifyTransition(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
transactionId: string,
|
||||||
|
ti: TransitionInfo | undefined,
|
||||||
|
): void {
|
||||||
|
if (
|
||||||
|
ti &&
|
||||||
|
!(
|
||||||
|
ti.oldTxState.major === ti.newTxState.major &&
|
||||||
|
ti.oldTxState.minor === ti.newTxState.minor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ws.notify({
|
||||||
|
type: NotificationType.TransactionStateTransition,
|
||||||
|
oldTxState: ti.oldTxState,
|
||||||
|
newTxState: ti.newTxState,
|
||||||
|
transactionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -132,6 +132,7 @@ import {
|
|||||||
import { PendingTaskType, isWithdrawableDenom } from "../index.js";
|
import { PendingTaskType, isWithdrawableDenom } from "../index.js";
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
|
notifyTransition,
|
||||||
stopLongpolling,
|
stopLongpolling,
|
||||||
} from "./transactions.js";
|
} from "./transactions.js";
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ export async function suspendWithdrawalTransaction(
|
|||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
});
|
});
|
||||||
stopLongpolling(ws, taskId);
|
stopLongpolling(ws, taskId);
|
||||||
const stateUpdate = await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
@ -198,24 +199,18 @@ export async function suspendWithdrawalTransaction(
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stateUpdate) {
|
const transactionId = constructTransactionIdentifier({
|
||||||
ws.notify({
|
tag: TransactionType.Withdrawal,
|
||||||
type: NotificationType.TransactionStateTransition,
|
withdrawalGroupId,
|
||||||
transactionId: constructTransactionIdentifier({
|
});
|
||||||
tag: TransactionType.Withdrawal,
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
withdrawalGroupId,
|
|
||||||
}),
|
|
||||||
oldTxState: stateUpdate.oldTxState,
|
|
||||||
newTxState: stateUpdate.newTxState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resumeWithdrawalTransaction(
|
export async function resumeWithdrawalTransaction(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
withdrawalGroupId: string,
|
withdrawalGroupId: string,
|
||||||
) {
|
) {
|
||||||
const stateUpdate = await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
@ -264,17 +259,11 @@ export async function resumeWithdrawalTransaction(
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stateUpdate) {
|
const transactionId = constructTransactionIdentifier({
|
||||||
ws.notify({
|
tag: TransactionType.Withdrawal,
|
||||||
type: NotificationType.TransactionStateTransition,
|
withdrawalGroupId,
|
||||||
transactionId: constructTransactionIdentifier({
|
});
|
||||||
tag: TransactionType.Withdrawal,
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
withdrawalGroupId,
|
|
||||||
}),
|
|
||||||
oldTxState: stateUpdate.oldTxState,
|
|
||||||
newTxState: stateUpdate.newTxState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function abortWithdrawalTransaction(
|
export async function abortWithdrawalTransaction(
|
||||||
@ -285,8 +274,12 @@ export async function abortWithdrawalTransaction(
|
|||||||
tag: PendingTaskType.Withdraw,
|
tag: PendingTaskType.Withdraw,
|
||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
});
|
});
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
stopLongpolling(ws, taskId);
|
stopLongpolling(ws, taskId);
|
||||||
const stateUpdate = await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
@ -339,18 +332,7 @@ export async function abortWithdrawalTransaction(
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
if (stateUpdate) {
|
|
||||||
ws.notify({
|
|
||||||
type: NotificationType.TransactionStateTransition,
|
|
||||||
transactionId: constructTransactionIdentifier({
|
|
||||||
tag: TransactionType.Withdrawal,
|
|
||||||
withdrawalGroupId,
|
|
||||||
}),
|
|
||||||
oldTxState: stateUpdate.oldTxState,
|
|
||||||
newTxState: stateUpdate.newTxState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called "cancel" in the spec right now,
|
// Called "cancel" in the spec right now,
|
||||||
@ -363,6 +345,10 @@ export async function cancelAbortingWithdrawalTransaction(
|
|||||||
tag: PendingTaskType.Withdraw,
|
tag: PendingTaskType.Withdraw,
|
||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
});
|
});
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
stopLongpolling(ws, taskId);
|
stopLongpolling(ws, taskId);
|
||||||
const stateUpdate = await ws.db
|
const stateUpdate = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
@ -392,21 +378,9 @@ export async function cancelAbortingWithdrawalTransaction(
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
notifyTransition(ws, transactionId, stateUpdate);
|
||||||
if (stateUpdate) {
|
|
||||||
ws.notify({
|
|
||||||
type: NotificationType.TransactionStateTransition,
|
|
||||||
transactionId: constructTransactionIdentifier({
|
|
||||||
tag: TransactionType.Withdrawal,
|
|
||||||
withdrawalGroupId,
|
|
||||||
}),
|
|
||||||
oldTxState: stateUpdate.oldTxState,
|
|
||||||
newTxState: stateUpdate.newTxState,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function computeWithdrawalTransactionStatus(
|
export function computeWithdrawalTransactionStatus(
|
||||||
wgRecord: WithdrawalGroupRecord,
|
wgRecord: WithdrawalGroupRecord,
|
||||||
): TransactionState {
|
): TransactionState {
|
||||||
@ -1140,6 +1114,10 @@ async function queryReserve(
|
|||||||
withdrawalGroupId: string,
|
withdrawalGroupId: string,
|
||||||
cancellationToken: CancellationToken,
|
cancellationToken: CancellationToken,
|
||||||
): Promise<{ ready: boolean }> {
|
): Promise<{ ready: boolean }> {
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
|
const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
|
||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
});
|
});
|
||||||
@ -1190,25 +1168,31 @@ async function queryReserve(
|
|||||||
|
|
||||||
logger.trace(`got reserve status ${j2s(result.response)}`);
|
logger.trace(`got reserve status ${j2s(result.response)}`);
|
||||||
|
|
||||||
await ws.db
|
const transitionResult = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
if (!wg) {
|
if (!wg) {
|
||||||
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
logger.warn(`withdrawal group ${withdrawalGroupId} not found`);
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
const txStateOld = computeWithdrawalTransactionStatus(wg);
|
||||||
wg.status = WithdrawalGroupStatus.Ready;
|
wg.status = WithdrawalGroupStatus.Ready;
|
||||||
|
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);
|
||||||
|
return {
|
||||||
|
oldTxState: txStateOld,
|
||||||
|
newTxState: txStateNew,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, transitionResult);
|
||||||
|
|
||||||
|
// FIXME: This notification is deprecated with DD37
|
||||||
ws.notify({
|
ws.notify({
|
||||||
type: NotificationType.WithdrawalGroupReserveReady,
|
type: NotificationType.WithdrawalGroupReserveReady,
|
||||||
transactionId: makeTransactionId(
|
transactionId,
|
||||||
TransactionType.Withdrawal,
|
|
||||||
withdrawalGroupId,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ready: true };
|
return { ready: true };
|
||||||
@ -1252,6 +1236,10 @@ export async function processWithdrawalGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup);
|
const retryTag = TaskIdentifiers.forWithdrawal(withdrawalGroup);
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
// We're already running!
|
// We're already running!
|
||||||
if (ws.activeLongpoll[retryTag]) {
|
if (ws.activeLongpoll[retryTag]) {
|
||||||
@ -1322,17 +1310,24 @@ export async function processWithdrawalGroup(
|
|||||||
|
|
||||||
if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
|
if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
|
||||||
logger.warn("Finishing empty withdrawal group (no denoms)");
|
logger.warn("Finishing empty withdrawal group (no denoms)");
|
||||||
await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
if (!wg) {
|
if (!wg) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
const txStatusOld = computeWithdrawalTransactionStatus(wg);
|
||||||
wg.status = WithdrawalGroupStatus.Finished;
|
wg.status = WithdrawalGroupStatus.Finished;
|
||||||
wg.timestampFinish = TalerProtocolTimestamp.now();
|
wg.timestampFinish = TalerProtocolTimestamp.now();
|
||||||
|
const txStatusNew = computeWithdrawalTransactionStatus(wg);
|
||||||
await tx.withdrawalGroups.put(wg);
|
await tx.withdrawalGroups.put(wg);
|
||||||
|
return {
|
||||||
|
oldTxState: txStatusOld,
|
||||||
|
newTxState: txStatusNew,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
return {
|
return {
|
||||||
type: OperationAttemptResultType.Finished,
|
type: OperationAttemptResultType.Finished,
|
||||||
result: undefined,
|
result: undefined,
|
||||||
@ -1421,6 +1416,7 @@ export async function processWithdrawalGroup(
|
|||||||
errorsPerCoin[x.coinIdx] = x.lastError;
|
errorsPerCoin[x.coinIdx] = x.lastError;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const oldTxState = computeWithdrawalTransactionStatus(wg);
|
||||||
logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
|
logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
|
||||||
if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
|
if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
|
||||||
finishedForFirstTime = true;
|
finishedForFirstTime = true;
|
||||||
@ -1428,10 +1424,15 @@ export async function processWithdrawalGroup(
|
|||||||
wg.status = WithdrawalGroupStatus.Finished;
|
wg.status = WithdrawalGroupStatus.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTxState = computeWithdrawalTransactionStatus(wg);
|
||||||
await tx.withdrawalGroups.put(wg);
|
await tx.withdrawalGroups.put(wg);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kycInfo: wg.kycPending,
|
kycInfo: wg.kycPending,
|
||||||
|
transitionInfo: {
|
||||||
|
oldTxState,
|
||||||
|
newTxState,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1439,6 +1440,8 @@ export async function processWithdrawalGroup(
|
|||||||
throw Error("withdrawal group does not exist anymore");
|
throw Error("withdrawal group does not exist anymore");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, res.transitionInfo);
|
||||||
|
|
||||||
const { kycInfo } = res;
|
const { kycInfo } = res;
|
||||||
|
|
||||||
if (numKycRequired > 0) {
|
if (numKycRequired > 0) {
|
||||||
@ -1478,6 +1481,7 @@ export async function processWithdrawalGroup(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Deprecated with DD37
|
||||||
if (finishedForFirstTime) {
|
if (finishedForFirstTime) {
|
||||||
ws.notify({
|
ws.notify({
|
||||||
type: NotificationType.WithdrawGroupFinished,
|
type: NotificationType.WithdrawGroupFinished,
|
||||||
@ -1838,6 +1842,10 @@ async function registerReserveWithBank(
|
|||||||
.runReadOnly(async (tx) => {
|
.runReadOnly(async (tx) => {
|
||||||
return await tx.withdrawalGroups.get(withdrawalGroupId);
|
return await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
});
|
});
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
switch (withdrawalGroup?.status) {
|
switch (withdrawalGroup?.status) {
|
||||||
case WithdrawalGroupStatus.WaitConfirmBank:
|
case WithdrawalGroupStatus.WaitConfirmBank:
|
||||||
case WithdrawalGroupStatus.RegisteringBank:
|
case WithdrawalGroupStatus.RegisteringBank:
|
||||||
@ -1860,19 +1868,21 @@ async function registerReserveWithBank(
|
|||||||
selected_exchange: bankInfo.exchangePaytoUri,
|
selected_exchange: bankInfo.exchangePaytoUri,
|
||||||
};
|
};
|
||||||
logger.info(`registering reserve with bank: ${j2s(reqBody)}`);
|
logger.info(`registering reserve with bank: ${j2s(reqBody)}`);
|
||||||
const httpResp = await ws.http.postJson(bankStatusUrl, reqBody, {
|
const httpResp = await ws.http.fetch(bankStatusUrl, {
|
||||||
|
method: "POST",
|
||||||
|
body: reqBody,
|
||||||
timeout: getReserveRequestTimeout(withdrawalGroup),
|
timeout: getReserveRequestTimeout(withdrawalGroup),
|
||||||
});
|
});
|
||||||
await readSuccessResponseJsonOrThrow(
|
await readSuccessResponseJsonOrThrow(
|
||||||
httpResp,
|
httpResp,
|
||||||
codecForBankWithdrawalOperationPostResponse(),
|
codecForBankWithdrawalOperationPostResponse(),
|
||||||
);
|
);
|
||||||
await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
switch (r.status) {
|
switch (r.status) {
|
||||||
case WithdrawalGroupStatus.RegisteringBank:
|
case WithdrawalGroupStatus.RegisteringBank:
|
||||||
@ -1887,9 +1897,18 @@ async function registerReserveWithBank(
|
|||||||
r.wgInfo.bankInfo.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(
|
r.wgInfo.bankInfo.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(
|
||||||
AbsoluteTime.now(),
|
AbsoluteTime.now(),
|
||||||
);
|
);
|
||||||
|
const oldTxState = computeWithdrawalTransactionStatus(r);
|
||||||
r.status = WithdrawalGroupStatus.WaitConfirmBank;
|
r.status = WithdrawalGroupStatus.WaitConfirmBank;
|
||||||
|
const newTxState = computeWithdrawalTransactionStatus(r);
|
||||||
await tx.withdrawalGroups.put(r);
|
await tx.withdrawalGroups.put(r);
|
||||||
|
return {
|
||||||
|
oldTxState,
|
||||||
|
newTxState,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
// FIXME: This notification is deprecated with DD37
|
||||||
ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
|
ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1904,6 +1923,10 @@ async function processReserveBankStatus(
|
|||||||
const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
|
const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, {
|
||||||
withdrawalGroupId,
|
withdrawalGroupId,
|
||||||
});
|
});
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
switch (withdrawalGroup?.status) {
|
switch (withdrawalGroup?.status) {
|
||||||
case WithdrawalGroupStatus.WaitConfirmBank:
|
case WithdrawalGroupStatus.WaitConfirmBank:
|
||||||
case WithdrawalGroupStatus.RegisteringBank:
|
case WithdrawalGroupStatus.RegisteringBank:
|
||||||
@ -1938,7 +1961,7 @@ async function processReserveBankStatus(
|
|||||||
|
|
||||||
if (status.aborted) {
|
if (status.aborted) {
|
||||||
logger.info("bank aborted the withdrawal");
|
logger.info("bank aborted the withdrawal");
|
||||||
await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
@ -1956,10 +1979,17 @@ async function processReserveBankStatus(
|
|||||||
throw Error("invariant failed");
|
throw Error("invariant failed");
|
||||||
}
|
}
|
||||||
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
|
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
|
||||||
|
const oldTxState = computeWithdrawalTransactionStatus(r);
|
||||||
r.wgInfo.bankInfo.timestampBankConfirmed = now;
|
r.wgInfo.bankInfo.timestampBankConfirmed = now;
|
||||||
r.status = WithdrawalGroupStatus.BankAborted;
|
r.status = WithdrawalGroupStatus.BankAborted;
|
||||||
|
const newTxState = computeWithdrawalTransactionStatus(r);
|
||||||
await tx.withdrawalGroups.put(r);
|
await tx.withdrawalGroups.put(r);
|
||||||
|
return {
|
||||||
|
oldTxState,
|
||||||
|
newTxState,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
return {
|
return {
|
||||||
status: BankStatusResultCode.Aborted,
|
status: BankStatusResultCode.Aborted,
|
||||||
};
|
};
|
||||||
@ -1977,12 +2007,12 @@ async function processReserveBankStatus(
|
|||||||
return await processReserveBankStatus(ws, withdrawalGroupId);
|
return await processReserveBankStatus(ws, withdrawalGroupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [x.withdrawalGroups])
|
.mktx((x) => [x.withdrawalGroups])
|
||||||
.runReadWrite(async (tx) => {
|
.runReadWrite(async (tx) => {
|
||||||
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
const r = await tx.withdrawalGroups.get(withdrawalGroupId);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
// Re-check reserve status within transaction
|
// Re-check reserve status within transaction
|
||||||
switch (r.status) {
|
switch (r.status) {
|
||||||
@ -1990,16 +2020,18 @@ async function processReserveBankStatus(
|
|||||||
case WithdrawalGroupStatus.WaitConfirmBank:
|
case WithdrawalGroupStatus.WaitConfirmBank:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
|
if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
|
||||||
throw Error("invariant failed");
|
throw Error("invariant failed");
|
||||||
}
|
}
|
||||||
|
const oldTxState = computeWithdrawalTransactionStatus(r);
|
||||||
if (status.transfer_done) {
|
if (status.transfer_done) {
|
||||||
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.QueryingStatus;
|
||||||
|
// FIXME: Notification is deprecated with DD37.
|
||||||
ws.notify({
|
ws.notify({
|
||||||
type: NotificationType.WithdrawalGroupBankConfirmed,
|
type: NotificationType.WithdrawalGroupBankConfirmed,
|
||||||
transactionId: makeTransactionId(
|
transactionId: makeTransactionId(
|
||||||
@ -2012,9 +2044,16 @@ async function processReserveBankStatus(
|
|||||||
r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url;
|
r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url;
|
||||||
r.senderWire = status.sender_wire;
|
r.senderWire = status.sender_wire;
|
||||||
}
|
}
|
||||||
|
const newTxState = computeWithdrawalTransactionStatus(r);
|
||||||
await tx.withdrawalGroups.put(r);
|
await tx.withdrawalGroups.put(r);
|
||||||
|
return {
|
||||||
|
oldTxState,
|
||||||
|
newTxState,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
|
||||||
if (status.transfer_done) {
|
if (status.transfer_done) {
|
||||||
return {
|
return {
|
||||||
status: BankStatusResultCode.Done,
|
status: BankStatusResultCode.Done,
|
||||||
@ -2071,6 +2110,11 @@ export async function internalCreateWithdrawalGroup(
|
|||||||
withdrawalGroupId = encodeCrock(getRandomBytes(32));
|
withdrawalGroupId = encodeCrock(getRandomBytes(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transactionId = constructTransactionIdentifier({
|
||||||
|
tag: TransactionType.Withdrawal,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
await updateWithdrawalDenoms(ws, canonExchange);
|
await updateWithdrawalDenoms(ws, canonExchange);
|
||||||
const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange);
|
const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange);
|
||||||
|
|
||||||
@ -2122,7 +2166,7 @@ export async function internalCreateWithdrawalGroup(
|
|||||||
exchangeInfo.exchange,
|
exchangeInfo.exchange,
|
||||||
);
|
);
|
||||||
|
|
||||||
await ws.db
|
const transitionInfo = await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
x.withdrawalGroups,
|
x.withdrawalGroups,
|
||||||
x.reserves,
|
x.reserves,
|
||||||
@ -2151,8 +2195,19 @@ export async function internalCreateWithdrawalGroup(
|
|||||||
uids: [encodeCrock(getRandomBytes(32))],
|
uids: [encodeCrock(getRandomBytes(32))],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldTxState = {
|
||||||
|
major: TransactionMajorState.None,
|
||||||
|
}
|
||||||
|
const newTxState = computeWithdrawalTransactionStatus(withdrawalGroup);
|
||||||
|
return {
|
||||||
|
oldTxState,
|
||||||
|
newTxState,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
notifyTransition(ws, transactionId, transitionInfo);
|
||||||
|
|
||||||
return withdrawalGroup;
|
return withdrawalGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2225,6 +2280,10 @@ export async function acceptWithdrawalFromUri(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
|
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
|
||||||
|
const transactionId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Withdraw,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
// We do this here, as the reserve should be registered before we return,
|
// We do this here, as the reserve should be registered before we return,
|
||||||
// so that we can redirect the user to the bank's status page.
|
// so that we can redirect the user to the bank's status page.
|
||||||
@ -2249,10 +2308,7 @@ export async function acceptWithdrawalFromUri(
|
|||||||
return {
|
return {
|
||||||
reservePub: withdrawalGroup.reservePub,
|
reservePub: withdrawalGroup.reservePub,
|
||||||
confirmTransferUrl: withdrawInfo.confirmTransferUrl,
|
confirmTransferUrl: withdrawInfo.confirmTransferUrl,
|
||||||
transactionId: makeTransactionId(
|
transactionId,
|
||||||
TransactionType.Withdrawal,
|
|
||||||
withdrawalGroupId,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2285,6 +2341,10 @@ export async function createManualWithdrawal(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
|
const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
|
||||||
|
const transactionId = constructTaskIdentifier({
|
||||||
|
tag: PendingTaskType.Withdraw,
|
||||||
|
withdrawalGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
const exchangePaytoUris = await ws.db
|
const exchangePaytoUris = await ws.db
|
||||||
.mktx((x) => [
|
.mktx((x) => [
|
||||||
@ -2313,9 +2373,6 @@ export async function createManualWithdrawal(
|
|||||||
return {
|
return {
|
||||||
reservePub: withdrawalGroup.reservePub,
|
reservePub: withdrawalGroup.reservePub,
|
||||||
exchangePaytoUris: exchangePaytoUris,
|
exchangePaytoUris: exchangePaytoUris,
|
||||||
transactionId: makeTransactionId(
|
transactionId,
|
||||||
TransactionType.Withdrawal,
|
|
||||||
withdrawalGroupId,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import {
|
|||||||
ApplyRefundResponse,
|
ApplyRefundResponse,
|
||||||
BackupRecovery,
|
BackupRecovery,
|
||||||
BalancesResponse,
|
BalancesResponse,
|
||||||
|
CancelAbortingTransactionRequest,
|
||||||
CheckPeerPullCreditRequest,
|
CheckPeerPullCreditRequest,
|
||||||
CheckPeerPullCreditResponse,
|
CheckPeerPullCreditResponse,
|
||||||
CheckPeerPushDebitRequest,
|
CheckPeerPushDebitRequest,
|
||||||
@ -156,6 +157,7 @@ export enum WalletApiOperation {
|
|||||||
GetExchangeDetailedInfo = "getExchangeDetailedInfo",
|
GetExchangeDetailedInfo = "getExchangeDetailedInfo",
|
||||||
RetryPendingNow = "retryPendingNow",
|
RetryPendingNow = "retryPendingNow",
|
||||||
AbortTransaction = "abortTransaction",
|
AbortTransaction = "abortTransaction",
|
||||||
|
CancelAbortingTransaction = "cancelAbortingTransaction",
|
||||||
SuspendTransaction = "suspendTransaction",
|
SuspendTransaction = "suspendTransaction",
|
||||||
ResumeTransaction = "resumeTransaction",
|
ResumeTransaction = "resumeTransaction",
|
||||||
ConfirmPay = "confirmPay",
|
ConfirmPay = "confirmPay",
|
||||||
@ -327,6 +329,17 @@ export type AbortTransactionOp = {
|
|||||||
response: EmptyObject;
|
response: EmptyObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel aborting a transaction
|
||||||
|
*
|
||||||
|
* For payment transactions, it puts the payment into an "aborting" state.
|
||||||
|
*/
|
||||||
|
export type CancelAbortingTransactionOp = {
|
||||||
|
op: WalletApiOperation.CancelAbortingTransaction;
|
||||||
|
request: CancelAbortingTransactionRequest;
|
||||||
|
response: EmptyObject;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suspend a transaction
|
* Suspend a transaction
|
||||||
*/
|
*/
|
||||||
@ -922,6 +935,7 @@ export type WalletOperations = {
|
|||||||
[WalletApiOperation.WithdrawTestkudos]: WithdrawTestkudosOp;
|
[WalletApiOperation.WithdrawTestkudos]: WithdrawTestkudosOp;
|
||||||
[WalletApiOperation.ConfirmPay]: ConfirmPayOp;
|
[WalletApiOperation.ConfirmPay]: ConfirmPayOp;
|
||||||
[WalletApiOperation.AbortTransaction]: AbortTransactionOp;
|
[WalletApiOperation.AbortTransaction]: AbortTransactionOp;
|
||||||
|
[WalletApiOperation.CancelAbortingTransaction]: CancelAbortingTransactionOp;
|
||||||
[WalletApiOperation.SuspendTransaction]: SuspendTransactionOp;
|
[WalletApiOperation.SuspendTransaction]: SuspendTransactionOp;
|
||||||
[WalletApiOperation.ResumeTransaction]: ResumeTransactionOp;
|
[WalletApiOperation.ResumeTransaction]: ResumeTransactionOp;
|
||||||
[WalletApiOperation.GetBalances]: GetBalancesOp;
|
[WalletApiOperation.GetBalances]: GetBalancesOp;
|
||||||
|
@ -1221,7 +1221,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
}
|
}
|
||||||
case WalletApiOperation.AbortTransaction: {
|
case WalletApiOperation.AbortTransaction: {
|
||||||
const req = codecForAbortTransaction().decode(payload);
|
const req = codecForAbortTransaction().decode(payload);
|
||||||
await abortTransaction(ws, req.transactionId, req.forceImmediateAbort);
|
await abortTransaction(ws, req.transactionId);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
case WalletApiOperation.SuspendTransaction: {
|
case WalletApiOperation.SuspendTransaction: {
|
||||||
|
Loading…
Reference in New Issue
Block a user