gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: further towards


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: further towards deposit DD37
Date: Sun, 23 Apr 2023 22:49:45 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new eff3920bd wallet-core: further towards deposit DD37
eff3920bd is described below

commit eff3920bd5a2bff58d66ac72ba8bd2c1577f452f
Author: Florian Dold <florian@dold.me>
AuthorDate: Sun Apr 23 22:49:42 2023 +0200

    wallet-core: further towards deposit DD37
---
 packages/taler-harness/src/index.ts                |  31 +-
 packages/taler-util/src/taler-crypto.ts            |  16 +
 packages/taler-wallet-cli/src/index.ts             |  38 +-
 .../src/crypto/cryptoImplementation.ts             |  27 ++
 .../taler-wallet-core/src/crypto/cryptoTypes.ts    |  15 +
 packages/taler-wallet-core/src/db.ts               |  26 +-
 .../taler-wallet-core/src/operations/deposits.ts   | 513 ++++++++++++++-------
 .../src/operations/transactions.ts                 |  26 +-
 8 files changed, 511 insertions(+), 181 deletions(-)

diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index e8eb57fe9..30b557986 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -267,7 +267,7 @@ deploymentCli
   });
 
 deploymentCli
-  .subcommand("testTalerdotnetDemo", "test-demo-talerdotnet")
+  .subcommand("testTalerdotnetDemo", "test-demodottalerdotnet")
   .action(async (args) => {
     const http = createPlatformHttpLib();
     const cryptiDisp = new CryptoDispatcher(
@@ -295,6 +295,35 @@ deploymentCli
     console.log("reserve status", reserveStatusResp.status);
   });
 
+deploymentCli
+  .subcommand("testDemoTestdotdalerdotnet", "test-testdottalerdotnet")
+  .action(async (args) => {
+    const http = createPlatformHttpLib();
+    const cryptiDisp = new CryptoDispatcher(
+      new SynchronousCryptoWorkerFactoryPlain(),
+    );
+    const cryptoApi = cryptiDisp.cryptoApi;
+    const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
+    const exchangeBaseUrl = "https://exchange.test.taler.net/";;
+    const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http);
+    await topupReserveWithDemobank({
+      amount: "TESTKUDOS:10",
+      bankAccessApiBaseUrl:
+        "https://bank.test.taler.net/demobanks/default/access-api/";,
+      exchangeInfo,
+      http,
+      reservePub: reserveKeyPair.pub,
+    });
+    let reserveUrl = new URL(`reserves/${reserveKeyPair.pub}`, 
exchangeBaseUrl);
+    reserveUrl.searchParams.set("timeout_ms", "30000");
+    console.log("requesting", reserveUrl.href);
+    const longpollReq = http.fetch(reserveUrl.href, {
+      method: "GET",
+    });
+    const reserveStatusResp = await longpollReq;
+    console.log("reserve status", reserveStatusResp.status);
+  });
+
 deploymentCli
   .subcommand("testLocalhostDemo", "test-demo-localhost")
   .action(async (args) => {
diff --git a/packages/taler-util/src/taler-crypto.ts 
b/packages/taler-util/src/taler-crypto.ts
index db64efcf2..fa92f5683 100644
--- a/packages/taler-util/src/taler-crypto.ts
+++ b/packages/taler-util/src/taler-crypto.ts
@@ -868,6 +868,21 @@ export function bufferForUint32(n: number): Uint8Array {
   return buf;
 }
 
+/**
+ * This makes the assumption that the uint64 fits a float,
+ * which should be true for all Taler protocol messages.
+ */
+export function bufferForUint64(n: number): Uint8Array {
+  const arrBuf = new ArrayBuffer(4);
+  const buf = new Uint8Array(arrBuf);
+  const dv = new DataView(arrBuf);
+  if (n < 0 || !Number.isInteger(n)) {
+    throw Error("non-negative integer expected");
+  }
+  dv.setBigUint64(0, BigInt(n));
+  return buf;
+}
+
 export function bufferForUint8(n: number): Uint8Array {
   const arrBuf = new ArrayBuffer(1);
   const buf = new Uint8Array(arrBuf);
@@ -933,6 +948,7 @@ export enum TalerSignaturePurpose {
   TEST = 4242,
   MERCHANT_PAYMENT_OK = 1104,
   MERCHANT_CONTRACT = 1101,
+  MERCHANT_REFUND = 1102,
   WALLET_COIN_RECOUP = 1203,
   WALLET_COIN_LINK = 1204,
   WALLET_COIN_RECOUP_REFRESH = 1206,
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index cc7c119b9..a31aec2eb 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -386,8 +386,12 @@ walletCli
 
 const transactionsCli = walletCli
   .subcommand("transactions", "transactions", { help: "Manage transactions." })
-  .maybeOption("currency", ["--currency"], clk.STRING)
-  .maybeOption("search", ["--search"], clk.STRING)
+  .maybeOption("currency", ["--currency"], clk.STRING, {
+    help: "Filter by currency.",
+  })
+  .maybeOption("search", ["--search"], clk.STRING, {
+    help: "Filter by search string",
+  })
   .flag("includeRefreshes", ["--include-refreshes"]);
 
 // Default action
@@ -420,6 +424,36 @@ transactionsCli
     });
   });
 
+transactionsCli
+  .subcommand("suspendTransaction", "suspend", {
+    help: "Suspend a transaction.",
+  })
+  .requiredArgument("transactionId", clk.STRING, {
+    help: "Identifier of the transaction to suspend.",
+  })
+  .action(async (args) => {
+    await withWallet(args, async (wallet) => {
+      await wallet.client.call(WalletApiOperation.SuspendTransaction, {
+        transactionId: args.suspendTransaction.transactionId,
+      });
+    });
+  });
+
+transactionsCli
+  .subcommand("resumeTransaction", "resume", {
+    help: "Resume a transaction.",
+  })
+  .requiredArgument("transactionId", clk.STRING, {
+    help: "Identifier of the transaction to suspend.",
+  })
+  .action(async (args) => {
+    await withWallet(args, async (wallet) => {
+      await wallet.client.call(WalletApiOperation.ResumeTransaction, {
+        transactionId: args.resumeTransaction.transactionId,
+      });
+    });
+  });
+
 transactionsCli
   .subcommand("lookup", "lookup", {
     help: "Look up a single transaction based on the transaction identifier.",
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 52d2dd24e..fa1271a7b 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -33,6 +33,7 @@ import {
   AmountString,
   BlindedDenominationSignature,
   bufferForUint32,
+  bufferForUint64,
   buildSigPS,
   CoinDepositPermission,
   CoinEnvelope,
@@ -105,6 +106,8 @@ import {
   EncryptedContract,
   SignPurseMergeRequest,
   SignPurseMergeResponse,
+  SignRefundRequest,
+  SignRefundResponse,
   SignReservePurseCreateRequest,
   SignReservePurseCreateResponse,
   SignTrackTransactionRequest,
@@ -233,6 +236,8 @@ export interface TalerCryptoInterface {
   signReservePurseCreate(
     req: SignReservePurseCreateRequest,
   ): Promise<SignReservePurseCreateResponse>;
+
+  signRefund(req: SignRefundRequest): Promise<SignRefundResponse>;
 }
 
 /**
@@ -409,6 +414,9 @@ export const nullCrypto: TalerCryptoInterface = {
   ): Promise<SignReservePurseCreateResponse> {
     throw new Error("Function not implemented.");
   },
+  signRefund: function (req: SignRefundRequest): Promise<SignRefundResponse> {
+    throw new Error("Function not implemented.");
+  },
 };
 
 export type WithArg<X> = X extends (req: infer T) => infer R
@@ -928,6 +936,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
     const pub = decodeCrock(masterPub);
     return { valid: eddsaVerify(p, sig, pub) };
   },
+
   /**
    * Check if the signature of a denomination is valid.
    */
@@ -1625,6 +1634,24 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
       purseSig: purseSigResp.sig,
     };
   },
+  async signRefund(
+    tci: TalerCryptoInterfaceR,
+    req: SignRefundRequest,
+  ): Promise<SignRefundResponse> {
+    const refundSigBlob = buildSigPS(TalerSignaturePurpose.MERCHANT_REFUND)
+      .put(decodeCrock(req.contractTermsHash))
+      .put(decodeCrock(req.coinPub))
+      .put(bufferForUint64(req.rtransactionId))
+      .put(amountToBuffer(req.refundAmount))
+      .build();
+    const refundSigResp = await tci.eddsaSign(tci, {
+      msg: encodeCrock(refundSigBlob),
+      priv: req.merchantPriv,
+    });
+    return {
+      sig: refundSigResp.sig,
+    };
+  },
 };
 
 function amountToBuffer(amount: AmountLike): Uint8Array {
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts 
b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
index 98f3c935b..3b27db0c0 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -255,6 +255,21 @@ export interface SignPurseMergeResponse {
   accountSig: string;
 }
 
+export interface SignRefundRequest {
+  merchantPriv: string;
+  merchantPub: string;
+  contractTermsHash: string;
+  coinPub: string;
+  rtransactionId: number;
+  refundAmount: AmountString;
+}
+
+export interface SignRefundResponse {
+  sig: string;
+}
+
+export interface SignRefundResponse {}
+
 export interface SignReservePurseCreateRequest {
   mergeTimestamp: TalerProtocolTimestamp;
 
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 0bfe11aaa..a8c103265 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -873,6 +873,8 @@ export enum DepositElementStatus {
   Accepted = 20,
   KycRequired = 30,
   Wired = 40,
+  RefundSuccess = 50,
+  RefundFailed = 51,
 }
 
 /**
@@ -1639,6 +1641,14 @@ export interface BackupProviderRecord {
   uids: string[];
 }
 
+export enum DepositOperationStatus {
+  Finished = 50 /* OperationStatusRange.DORMANT_START */,
+  Suspended = 51 /* OperationStatusRange.DORMANT_START + 1 */,
+  Aborted = 52 /* OperationStatusRange.DORMANT_START + 2 */,
+  Pending = 10 /* OperationStatusRange.ACTIVE_START */,
+  Aborting = 11 /* OperationStatusRange.ACTIVE_START + 1 */,
+}
+
 /**
  * Group of deposits made by the wallet.
  */
@@ -1680,16 +1690,26 @@ export interface DepositGroupRecord {
    */
   effectiveDepositAmount: AmountString;
 
-  depositedPerCoin: boolean[];
-
   timestampCreated: TalerProtocolTimestamp;
 
   timestampFinished: TalerProtocolTimestamp | undefined;
 
-  operationStatus: OperationStatus;
+  operationStatus: DepositOperationStatus;
 
+  // FIXME: Duplication between this and transactionPerCoin!
+  depositedPerCoin: boolean[];
+
+  // FIXME: Improve name!
   transactionPerCoin: DepositElementStatus[];
 
+  /**
+   * When the deposit transaction was aborted and
+   * refreshes were tried, we create a refresh
+   * group and store the ID here.
+   */
+  abortRefreshGroupId?: string;
+
+  // FIXME: Do we need this and should it be in this object store?
   trackingState?: {
     [signature: string]: {
       // Raw wire transfer identifier of the deposit.
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 6e56b0897..051cbc176 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -64,13 +64,16 @@ import {
   DepositElementStatus,
 } from "../db.js";
 import { TalerError } from "@gnu-taler/taler-util";
-import { getTotalRefreshCost, KycPendingInfo, KycUserType } from "../index.js";
+import {
+  DepositOperationStatus,
+  getTotalRefreshCost,
+  KycPendingInfo,
+  KycUserType,
+  PendingTaskType,
+} from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
-import {
-  OperationAttemptResult,
-  OperationAttemptResultType,
-} from "../util/retries.js";
+import { OperationAttemptResult } from "../util/retries.js";
 import { spendCoins } from "./common.js";
 import { getExchangeDetails } from "./exchanges.js";
 import {
@@ -82,7 +85,9 @@ import { selectPayCoinsNew } from "../util/coinSelection.js";
 import {
   constructTransactionIdentifier,
   parseTransactionIdentifier,
+  stopLongpolling,
 } from "./transactions.js";
+import { constructTaskIdentifier } from "../util/retries.js";
 
 /**
  * Logger.
@@ -97,12 +102,12 @@ export function computeDepositTransactionStatus(
   dg: DepositGroupRecord,
 ): TransactionState {
   switch (dg.operationStatus) {
-    case OperationStatus.Finished: {
+    case DepositOperationStatus.Finished: {
       return {
         major: TransactionMajorState.Done,
       };
     }
-    case OperationStatus.Pending: {
+    case DepositOperationStatus.Pending: {
       const numTotal = dg.payCoinSelection.coinPubs.length;
       let numDeposited = 0;
       let numKycRequired = 0;
@@ -140,6 +145,10 @@ export function computeDepositTransactionStatus(
         minor: TransactionMinorState.Deposit,
       };
     }
+    case DepositOperationStatus.Suspended:
+      return {
+        major: TransactionMajorState.Suspended,
+      };
     default:
       throw Error("unexpected deposit group state");
   }
@@ -149,13 +158,156 @@ export async function suspendDepositGroup(
   ws: InternalWalletState,
   depositGroupId: string,
 ): Promise<void> {
-  throw Error("not implemented");
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Deposit,
+    depositGroupId,
+  });
+  const retryTag = constructTaskIdentifier({
+    tag: PendingTaskType.Deposit,
+    depositGroupId,
+  });
+  let res = await ws.db
+    .mktx((x) => [x.depositGroups])
+    .runReadWrite(async (tx) => {
+      const dg = await tx.depositGroups.get(depositGroupId);
+      if (!dg) {
+        logger.warn(
+          `can't suspend deposit group, depositGroupId=${depositGroupId} not 
found`,
+        );
+        return undefined;
+      }
+      const oldState = computeDepositTransactionStatus(dg);
+      switch (dg.operationStatus) {
+        case DepositOperationStatus.Finished:
+          return undefined;
+        case DepositOperationStatus.Pending: {
+          dg.operationStatus = DepositOperationStatus.Suspended;
+          await tx.depositGroups.put(dg);
+          return {
+            oldTxState: oldState,
+            newTxState: computeDepositTransactionStatus(dg),
+          };
+        }
+        case DepositOperationStatus.Suspended:
+          return undefined;
+      }
+      return undefined;
+    });
+  stopLongpolling(ws, retryTag);
+  if (res) {
+    ws.notify({
+      type: NotificationType.TransactionStateTransition,
+      transactionId,
+      oldTxState: res.oldTxState,
+      newTxState: res.newTxState,
+    });
+  }
+}
+
+export async function resumeDepositGroup(
+  ws: InternalWalletState,
+  depositGroupId: string,
+): Promise<void> {
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Deposit,
+    depositGroupId,
+  });
+  let res = await ws.db
+    .mktx((x) => [x.depositGroups])
+    .runReadWrite(async (tx) => {
+      const dg = await tx.depositGroups.get(depositGroupId);
+      if (!dg) {
+        logger.warn(
+          `can't resume deposit group, depositGroupId=${depositGroupId} not 
found`,
+        );
+        return;
+      }
+      const oldState = computeDepositTransactionStatus(dg);
+      switch (dg.operationStatus) {
+        case DepositOperationStatus.Finished:
+          return;
+        case DepositOperationStatus.Pending: {
+          return;
+        }
+        case DepositOperationStatus.Suspended:
+          dg.operationStatus = DepositOperationStatus.Pending;
+          await tx.depositGroups.put(dg);
+          return {
+            oldTxState: oldState,
+            newTxState: computeDepositTransactionStatus(dg),
+          };
+      }
+      return undefined;
+    });
+  ws.latch.trigger();
+  if (res) {
+    ws.notify({
+      type: NotificationType.TransactionStateTransition,
+      transactionId,
+      oldTxState: res.oldTxState,
+      newTxState: res.newTxState,
+    });
+  }
 }
 
 export async function abortDepositGroup(
   ws: InternalWalletState,
   depositGroupId: string,
 ): Promise<void> {
+  const transactionId = constructTransactionIdentifier({
+    tag: TransactionType.Deposit,
+    depositGroupId,
+  });
+  const retryTag = constructTaskIdentifier({
+    tag: PendingTaskType.Deposit,
+    depositGroupId,
+  });
+  let res = await ws.db
+    .mktx((x) => [x.depositGroups])
+    .runReadWrite(async (tx) => {
+      const dg = await tx.depositGroups.get(depositGroupId);
+      if (!dg) {
+        logger.warn(
+          `can't suspend deposit group, depositGroupId=${depositGroupId} not 
found`,
+        );
+        return undefined;
+      }
+      const oldState = computeDepositTransactionStatus(dg);
+      switch (dg.operationStatus) {
+        case DepositOperationStatus.Finished:
+          return undefined;
+        case DepositOperationStatus.Pending: {
+          dg.operationStatus = DepositOperationStatus.Aborting;
+          await tx.depositGroups.put(dg);
+          return {
+            oldTxState: oldState,
+            newTxState: computeDepositTransactionStatus(dg),
+          };
+        }
+        case DepositOperationStatus.Suspended:
+          // FIXME: Can we abort a suspended transaction?!
+          return undefined;
+      }
+      return undefined;
+    });
+  stopLongpolling(ws, retryTag);
+  // Need to process the operation again.
+  ws.latch.trigger();
+  if (res) {
+    ws.notify({
+      type: NotificationType.TransactionStateTransition,
+      transactionId,
+      oldTxState: res.oldTxState,
+      newTxState: res.newTxState,
+    });
+  }
+}
+
+export async function deleteDepositGroup(
+  ws: InternalWalletState,
+  depositGroupId: boolean,
+  opts: { forced?: boolean } = {},
+) {
   throw Error("not implemented");
 }
 
@@ -230,195 +382,210 @@ export async function processDepositGroup(
 
   const txStateOld = computeDepositTransactionStatus(depositGroup);
 
-  const contractData = extractContractData(
-    depositGroup.contractTermsRaw,
-    depositGroup.contractTermsHash,
-    "",
-  );
+  if (depositGroup.operationStatus === DepositOperationStatus.Pending) {
+    const contractData = extractContractData(
+      depositGroup.contractTermsRaw,
+      depositGroup.contractTermsHash,
+      "",
+    );
 
-  // Check for cancellation before expensive operations.
-  options.cancellationToken?.throwIfCancelled();
-  // FIXME: Cache these!
-  const depositPermissions = await generateDepositPermissions(
-    ws,
-    depositGroup.payCoinSelection,
-    contractData,
-  );
+    // Check for cancellation before expensive operations.
+    options.cancellationToken?.throwIfCancelled();
+    // FIXME: Cache these!
+    const depositPermissions = await generateDepositPermissions(
+      ws,
+      depositGroup.payCoinSelection,
+      contractData,
+    );
 
-  for (let i = 0; i < depositPermissions.length; i++) {
-    const perm = depositPermissions[i];
-
-    let updatedDeposit: boolean = false;
-
-    if (!depositGroup.depositedPerCoin[i]) {
-      const requestBody: ExchangeDepositRequest = {
-        contribution: Amounts.stringify(perm.contribution),
-        merchant_payto_uri: depositGroup.wire.payto_uri,
-        wire_salt: depositGroup.wire.salt,
-        h_contract_terms: depositGroup.contractTermsHash,
-        ub_sig: perm.ub_sig,
-        timestamp: depositGroup.contractTermsRaw.timestamp,
-        wire_transfer_deadline:
-          depositGroup.contractTermsRaw.wire_transfer_deadline,
-        refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
-        coin_sig: perm.coin_sig,
-        denom_pub_hash: perm.h_denom,
-        merchant_pub: depositGroup.merchantPub,
-        h_age_commitment: perm.h_age_commitment,
-      };
-      // Check for cancellation before making network request.
-      options.cancellationToken?.throwIfCancelled();
-      const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
-      logger.info(`depositing to ${url}`);
-      const httpResp = await ws.http.fetch(url.href, {
-        method: "POST",
-        body: requestBody,
-        cancellationToken: options.cancellationToken,
-      });
-      await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
-      updatedDeposit = true;
-    }
+    for (let i = 0; i < depositPermissions.length; i++) {
+      const perm = depositPermissions[i];
+
+      let updatedDeposit: boolean = false;
+
+      if (!depositGroup.depositedPerCoin[i]) {
+        const requestBody: ExchangeDepositRequest = {
+          contribution: Amounts.stringify(perm.contribution),
+          merchant_payto_uri: depositGroup.wire.payto_uri,
+          wire_salt: depositGroup.wire.salt,
+          h_contract_terms: depositGroup.contractTermsHash,
+          ub_sig: perm.ub_sig,
+          timestamp: depositGroup.contractTermsRaw.timestamp,
+          wire_transfer_deadline:
+            depositGroup.contractTermsRaw.wire_transfer_deadline,
+          refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
+          coin_sig: perm.coin_sig,
+          denom_pub_hash: perm.h_denom,
+          merchant_pub: depositGroup.merchantPub,
+          h_age_commitment: perm.h_age_commitment,
+        };
+        // Check for cancellation before making network request.
+        options.cancellationToken?.throwIfCancelled();
+        const url = new URL(
+          `coins/${perm.coin_pub}/deposit`,
+          perm.exchange_url,
+        );
+        logger.info(`depositing to ${url}`);
+        const httpResp = await ws.http.fetch(url.href, {
+          method: "POST",
+          body: requestBody,
+          cancellationToken: options.cancellationToken,
+        });
+        await readSuccessResponseJsonOrThrow(
+          httpResp,
+          codecForDepositSuccess(),
+        );
+        updatedDeposit = true;
+      }
 
-    let updatedTxStatus: DepositElementStatus | undefined = undefined;
-    type ValueOf<T> = T[keyof T];
+      let updatedTxStatus: DepositElementStatus | undefined = undefined;
+      type ValueOf<T> = T[keyof T];
 
-    let newWiredTransaction:
-      | {
-          id: string;
-          value: ValueOf<NonNullable<DepositGroupRecord["trackingState"]>>;
-        }
-      | undefined;
+      let newWiredTransaction:
+        | {
+            id: string;
+            value: ValueOf<NonNullable<DepositGroupRecord["trackingState"]>>;
+          }
+        | undefined;
 
-    if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) {
-      const track = await trackDeposit(ws, depositGroup, perm);
+      if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) {
+        const track = await trackDeposit(ws, depositGroup, perm);
 
-      if (track.type === "accepted") {
-        if (!track.kyc_ok && track.requirement_row !== undefined) {
-          updatedTxStatus = DepositElementStatus.KycRequired;
-          const { requirement_row: requirementRow } = track;
-          const paytoHash = encodeCrock(
-            hashTruncate32(stringToBytes(depositGroup.wire.payto_uri + "\0")),
-          );
-          await checkDepositKycStatus(
+        if (track.type === "accepted") {
+          if (!track.kyc_ok && track.requirement_row !== undefined) {
+            updatedTxStatus = DepositElementStatus.KycRequired;
+            const { requirement_row: requirementRow } = track;
+            const paytoHash = encodeCrock(
+              hashTruncate32(stringToBytes(depositGroup.wire.payto_uri + 
"\0")),
+            );
+            await checkDepositKycStatus(
+              ws,
+              perm.exchange_url,
+              { paytoHash, requirementRow },
+              "individual",
+            );
+          } else {
+            updatedTxStatus = DepositElementStatus.Accepted;
+          }
+        } else if (track.type === "wired") {
+          updatedTxStatus = DepositElementStatus.Wired;
+
+          const payto = parsePaytoUri(depositGroup.wire.payto_uri);
+          if (!payto) {
+            throw Error(`unparsable payto: ${depositGroup.wire.payto_uri}`);
+          }
+
+          const fee = await getExchangeWireFee(
             ws,
+            payto.targetType,
             perm.exchange_url,
-            { paytoHash, requirementRow },
-            "individual",
+            track.execution_time,
           );
+          const raw = Amounts.parseOrThrow(track.coin_contribution);
+          const wireFee = Amounts.parseOrThrow(fee.wireFee);
+
+          newWiredTransaction = {
+            value: {
+              amountRaw: Amounts.stringify(raw),
+              wireFee: Amounts.stringify(wireFee),
+              exchangePub: track.exchange_pub,
+              timestampExecuted: track.execution_time,
+              wireTransferId: track.wtid,
+            },
+            id: track.exchange_sig,
+          };
         } else {
-          updatedTxStatus = DepositElementStatus.Accepted;
+          updatedTxStatus = DepositElementStatus.Unknown;
         }
-      } else if (track.type === "wired") {
-        updatedTxStatus = DepositElementStatus.Wired;
+      }
 
-        const payto = parsePaytoUri(depositGroup.wire.payto_uri);
-        if (!payto) {
-          throw Error(`unparsable payto: ${depositGroup.wire.payto_uri}`);
-        }
+      if (updatedTxStatus !== undefined || updatedDeposit) {
+        await ws.db
+          .mktx((x) => [x.depositGroups])
+          .runReadWrite(async (tx) => {
+            const dg = await tx.depositGroups.get(depositGroupId);
+            if (!dg) {
+              return;
+            }
+            if (updatedDeposit !== undefined) {
+              dg.depositedPerCoin[i] = updatedDeposit;
+            }
+            if (updatedTxStatus !== undefined) {
+              dg.transactionPerCoin[i] = updatedTxStatus;
+            }
+            if (newWiredTransaction) {
+              if (!dg.trackingState) {
+                dg.trackingState = {};
+              }
 
-        const fee = await getExchangeWireFee(
-          ws,
-          payto.targetType,
-          perm.exchange_url,
-          track.execution_time,
-        );
-        const raw = Amounts.parseOrThrow(track.coin_contribution);
-        const wireFee = Amounts.parseOrThrow(fee.wireFee);
-
-        newWiredTransaction = {
-          value: {
-            amountRaw: Amounts.stringify(raw),
-            wireFee: Amounts.stringify(wireFee),
-            exchangePub: track.exchange_pub,
-            timestampExecuted: track.execution_time,
-            wireTransferId: track.wtid,
-          },
-          id: track.exchange_sig,
-        };
-      } else {
-        updatedTxStatus = DepositElementStatus.Unknown;
+              dg.trackingState[newWiredTransaction.id] =
+                newWiredTransaction.value;
+            }
+            await tx.depositGroups.put(dg);
+          });
       }
     }
 
-    if (updatedTxStatus !== undefined || updatedDeposit) {
-      await ws.db
-        .mktx((x) => [x.depositGroups])
-        .runReadWrite(async (tx) => {
-          const dg = await tx.depositGroups.get(depositGroupId);
-          if (!dg) {
-            return;
-          }
-          if (updatedDeposit !== undefined) {
-            dg.depositedPerCoin[i] = updatedDeposit;
-          }
-          if (updatedTxStatus !== undefined) {
-            dg.transactionPerCoin[i] = updatedTxStatus;
-          }
-          if (newWiredTransaction) {
-            if (!dg.trackingState) {
-              dg.trackingState = {};
-            }
-
-            dg.trackingState[newWiredTransaction.id] =
-              newWiredTransaction.value;
+    const txStatusNew = await ws.db
+      .mktx((x) => [x.depositGroups])
+      .runReadWrite(async (tx) => {
+        const dg = await tx.depositGroups.get(depositGroupId);
+        if (!dg) {
+          return undefined;
+        }
+        let allDepositedAndWired = true;
+        for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
+          if (
+            !depositGroup.depositedPerCoin[i] ||
+            depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired
+          ) {
+            allDepositedAndWired = false;
+            break;
           }
+        }
+        if (allDepositedAndWired) {
+          dg.timestampFinished = TalerProtocolTimestamp.now();
+          dg.operationStatus = DepositOperationStatus.Finished;
           await tx.depositGroups.put(dg);
-        });
-    }
-  }
-
-  const txStatusNew = await ws.db
-    .mktx((x) => [x.depositGroups])
-    .runReadWrite(async (tx) => {
-      const dg = await tx.depositGroups.get(depositGroupId);
-      if (!dg) {
-        return undefined;
-      }
-      let allDepositedAndWired = true;
-      for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
-        if (
-          !depositGroup.depositedPerCoin[i] ||
-          depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired
-        ) {
-          allDepositedAndWired = false;
-          break;
         }
-      }
-      if (allDepositedAndWired) {
-        dg.timestampFinished = TalerProtocolTimestamp.now();
-        dg.operationStatus = OperationStatus.Finished;
-        await tx.depositGroups.put(dg);
-      }
-      return computeDepositTransactionStatus(dg);
-    });
+        return computeDepositTransactionStatus(dg);
+      });
 
-  if (!txStatusNew) {
-    // Doesn't exist anymore!
-    return OperationAttemptResult.finishedEmpty();
-  }
+    if (!txStatusNew) {
+      // Doesn't exist anymore!
+      return OperationAttemptResult.finishedEmpty();
+    }
 
-  // Notify if state transitioned
-  if (
-    txStateOld.major !== txStatusNew.major ||
-    txStateOld.minor !== txStatusNew.minor
-  ) {
-    ws.notify({
-      type: NotificationType.TransactionStateTransition,
-      transactionId,
-      oldTxState: txStateOld,
-      newTxState: txStatusNew,
-    });
+    // Notify if state transitioned
+    if (
+      txStateOld.major !== txStatusNew.major ||
+      txStateOld.minor !== txStatusNew.minor
+    ) {
+      ws.notify({
+        type: NotificationType.TransactionStateTransition,
+        transactionId,
+        oldTxState: txStateOld,
+        newTxState: txStatusNew,
+      });
+    }
+
+    // FIXME: consider other cases like aborting, suspend, ...
+    if (
+      txStatusNew.major === TransactionMajorState.Pending ||
+      txStatusNew.major === TransactionMajorState.Aborting
+    ) {
+      return OperationAttemptResult.pendingEmpty();
+    } else {
+      return OperationAttemptResult.finishedEmpty();
+    }
   }
 
-  // FIXME: consider other cases like aborting, suspend, ...
-  if (
-    txStatusNew.major === TransactionMajorState.Pending ||
-    txStatusNew.major === TransactionMajorState.Aborting
-  ) {
+  if (depositGroup.operationStatus === DepositOperationStatus.Aborting) {
+    // FIXME: Implement!
     return OperationAttemptResult.pendingEmpty();
-  } else {
-    return OperationAttemptResult.finishedEmpty();
   }
+
+  return OperationAttemptResult.finishedEmpty();
 }
 
 async function getExchangeWireFee(
@@ -763,7 +930,7 @@ export async function createDepositGroup(
       payto_uri: req.depositPaytoUri,
       salt: wireSalt,
     },
-    operationStatus: OperationStatus.Pending,
+    operationStatus: DepositOperationStatus.Pending,
   };
 
   const transactionId = constructTransactionIdentifier({
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 884844ba6..1a511583a 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -81,6 +81,7 @@ import {
 import {
   computeDepositTransactionStatus,
   processDepositGroup,
+  suspendDepositGroup,
 } from "./deposits.js";
 import { getExchangeDetails } from "./exchanges.js";
 import {
@@ -1615,7 +1616,19 @@ export async function retryTransaction(
 export async function suspendTransaction(
   ws: InternalWalletState,
   transactionId: string,
-): Promise<void> {}
+): Promise<void> {
+  const tx = parseTransactionIdentifier(transactionId);
+  if (!tx) {
+    throw Error("invalid transaction ID");
+  }
+  switch (tx.tag) {
+    case TransactionType.Deposit:
+      await suspendDepositGroup(ws, tx.depositGroupId);
+      return;
+    default:
+      logger.warn(`unable to suspend transaction of type '${tx.tag}'`);
+  }
+}
 
 /**
  * Resume a suspended transaction.
@@ -1623,7 +1636,16 @@ export async function suspendTransaction(
 export async function resumeTransaction(
   ws: InternalWalletState,
   transactionId: string,
-): Promise<void> {}
+): Promise<void> {
+  const tx = parseTransactionIdentifier(transactionId);
+  if (!tx) {
+    throw Error("invalid transaction ID");
+  }
+  switch (tx.tag) {
+    default:
+      logger.warn(`unable to resume transaction of type '${tx.tag}'`);
+  }
+}
 
 /**
  * Permanently delete a transaction based on the transaction ID.

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]