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: update KYC imple


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: update KYC implementation for peer-pull, add test
Date: Mon, 19 Aug 2024 18:19:39 +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 093bd7c9b wallet-core: update KYC implementation for peer-pull, add 
test
093bd7c9b is described below

commit 093bd7c9b69ccbddfa5d651640ed2da6953ab7f5
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Aug 19 18:19:29 2024 +0200

    wallet-core: update KYC implementation for peer-pull, add test
---
 .../src/integrationtests/test-kyc-peer-pull.ts     | 356 +++++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |   2 +
 .../taler-wallet-core/src/pay-peer-pull-credit.ts  |  58 ++--
 3 files changed, 387 insertions(+), 29 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts 
b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
new file mode 100644
index 000000000..c6de961ab
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
@@ -0,0 +1,356 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+  AbsoluteTime,
+  AmountString,
+  Duration,
+  j2s,
+  TalerCorebankApiClient,
+  TransactionIdStr,
+  TransactionMajorState,
+  TransactionMinorState,
+  TransactionType,
+} from "@gnu-taler/taler-util";
+import {
+  createSyncCryptoApi,
+  EddsaKeypair,
+  WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+  BankService,
+  DbInfo,
+  ExchangeService,
+  generateRandomPayto,
+  GlobalTestState,
+  HarnessExchangeBankAccount,
+  setupDb,
+  WalletClient,
+  WalletService,
+} from "../harness/harness.js";
+import {
+  createWalletDaemonWithClient,
+  EnvOptions,
+  postAmlDecisionNoRules,
+  withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+  commonDb: DbInfo;
+  bankClient: TalerCorebankApiClient;
+  exchange: ExchangeService;
+  exchangeBankAccount: HarnessExchangeBankAccount;
+  walletClient: WalletClient;
+  walletService: WalletService;
+  amlKeypair: EddsaKeypair;
+}
+
+async function createKycTestkudosEnvironment(
+  t: GlobalTestState,
+  coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+  opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+  const db = await setupDb(t);
+
+  const bank = await BankService.create(t, {
+    allowRegistrations: true,
+    currency: "TESTKUDOS",
+    database: db.connStr,
+    httpPort: 8082,
+  });
+
+  const exchange = ExchangeService.create(t, {
+    name: "testexchange-1",
+    currency: "TESTKUDOS",
+    httpPort: 8081,
+    database: db.connStr,
+  });
+
+  let receiverName = "Exchange";
+  let exchangeBankUsername = "exchange";
+  let exchangeBankPassword = "mypw";
+  let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+
+  await exchange.addBankAccount("1", {
+    accountName: exchangeBankUsername,
+    accountPassword: exchangeBankPassword,
+    wireGatewayApiBaseUrl: new URL(
+      "accounts/exchange/taler-wire-gateway/",
+      bank.baseUrl,
+    ).href,
+    accountPaytoUri: exchangePaytoUri,
+  });
+
+  bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+  await bank.start();
+
+  await bank.pingUntilAvailable();
+
+  const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+    auth: {
+      username: "admin",
+      password: "adminpw",
+    },
+  });
+
+  await bankClient.registerAccountExtended({
+    name: receiverName,
+    password: exchangeBankPassword,
+    username: exchangeBankUsername,
+    is_taler_exchange: true,
+    payto_uri: exchangePaytoUri,
+  });
+
+  const ageMaskSpec = opts.ageMaskSpec;
+
+  if (ageMaskSpec) {
+    exchange.enableAgeRestrictions(ageMaskSpec);
+    // Enable age restriction for all coins.
+    exchange.addCoinConfigList(
+      coinConfig.map((x) => ({
+        ...x,
+        name: `${x.name}-age`,
+        ageRestricted: true,
+      })),
+    );
+    // For mixed age restrictions, we also offer coins without age restrictions
+    if (opts.mixedAgeRestriction) {
+      exchange.addCoinConfigList(
+        coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+      );
+    }
+  } else {
+    exchange.addCoinConfigList(coinConfig);
+  }
+
+  await exchange.modifyConfig(async (config) => {
+    config.setString("exchange", "enable_kyc", "yes");
+
+    config.setString("KYC-RULE-R1", "operation_type", "merge");
+    config.setString("KYC-RULE-R1", "enabled", "yes");
+    config.setString("KYC-RULE-R1", "exposed", "yes");
+    config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+    config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+    config.setString("KYC-RULE-R1", "timeframe", "1d");
+    config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+    config.setString("KYC-MEASURE-M1", "check_name", "C1");
+    config.setString("KYC-MEASURE-M1", "context", "{}");
+    config.setString("KYC-MEASURE-M1", "program", "P1");
+
+    config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+    config.setString("AML-PROGRAM-P1", "enabled", "true");
+    config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+    config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+    config.setString("KYC-CHECK-C1", "type", "INFO");
+    config.setString("KYC-CHECK-C1", "description", "my check!");
+    config.setString("KYC-CHECK-C1", "fallback", "M1");
+  });
+
+  await exchange.start();
+
+  const cryptoApi = createSyncCryptoApi();
+  const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+  await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+  const walletService = new WalletService(t, {
+    name: "wallet",
+    useInMemoryDb: true,
+  });
+  await walletService.start();
+  await walletService.pingUntilAvailable();
+
+  const walletClient = new WalletClient({
+    name: "wallet",
+    unixPath: walletService.socketPath,
+    onNotification(n) {
+      console.log("got notification", n);
+    },
+  });
+  await walletClient.connect();
+  await walletClient.client.call(WalletApiOperation.InitWallet, {
+    config: {
+      testing: {
+        skipDefaults: true,
+      },
+    },
+  });
+
+  console.log("setup done!");
+
+  return {
+    commonDb: db,
+    exchange,
+    amlKeypair,
+    walletClient,
+    walletService,
+    bankClient,
+    exchangeBankAccount: {
+      accountName: "",
+      accountPassword: "",
+      accountPaytoUri: "",
+      wireGatewayApiBaseUrl: "",
+    },
+  };
+}
+
+export async function runKycPeerPullTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const { walletClient, bankClient, exchange, amlKeypair } =
+    await createKycTestkudosEnvironment(t);
+
+  // Origin wallet for the p2p transaction,
+  // will pay for the invoice.
+  const w0 = await createWalletDaemonWithClient(t, {
+    name: "w0",
+  });
+
+  // Withdraw digital cash into the wallet.
+
+  const wres1 = await withdrawViaBankV3(t, {
+    bankClient,
+    amount: "TESTKUDOS:20",
+    exchange: exchange,
+    walletClient: w0.walletClient,
+  });
+
+  await wres1.withdrawalFinishedCond;
+
+  const wres2 = await withdrawViaBankV3(t, {
+    bankClient,
+    amount: "TESTKUDOS:1",
+    exchange: exchange,
+    walletClient: walletClient,
+  });
+
+  await wres2.withdrawalFinishedCond;
+
+  const pullRes = await doPeerPullCredit(t, {
+    walletClient,
+    amount: "TESTKUDOS:10",
+    summary: "test123",
+  });
+
+  const prepRes = await w0.walletClient.call(
+    WalletApiOperation.PreparePeerPullDebit,
+    {
+      talerUri: pullRes.talerUri,
+    },
+  );
+
+  await w0.walletClient.call(WalletApiOperation.ConfirmPeerPullDebit, {
+    transactionId: prepRes.transactionId,
+  });
+
+  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+    transactionId: pullRes.transactionId as TransactionIdStr,
+    txState: {
+      major: TransactionMajorState.Pending,
+      minor: TransactionMinorState.MergeKycRequired,
+    },
+  });
+
+  const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, 
{
+    transactionId: pullRes.transactionId,
+  });
+
+  console.log("tx details", j2s(txDet));
+
+  const kycPaytoHash = txDet.kycPaytoHash;
+
+  t.assertTrue(!!kycPaytoHash);
+
+  await postAmlDecisionNoRules(t, {
+    amlPriv: amlKeypair.priv,
+    amlPub: amlKeypair.pub,
+    exchangeBaseUrl: exchange.baseUrl,
+    paytoHash: kycPaytoHash,
+  });
+
+  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+    transactionId: pullRes.transactionId as TransactionIdStr,
+    txState: {
+      major: TransactionMajorState.Done,
+    },
+  });
+}
+
+/**
+ * Initiate a pull debit transaction, wait until the transaction
+ * is ready.
+ */
+async function doPeerPullCredit(
+  t: GlobalTestState,
+  args: {
+    walletClient: WalletClient;
+    amount: AmountString;
+    summary?: string;
+  },
+): Promise<{
+  transactionId: string;
+  talerUri: string;
+}> {
+  const purse_expiration = AbsoluteTime.toProtocolTimestamp(
+    AbsoluteTime.addDuration(
+      AbsoluteTime.now(),
+      Duration.fromSpec({ days: 2 }),
+    ),
+  );
+  const initRet = await args.walletClient.call(
+    WalletApiOperation.InitiatePeerPullCredit,
+    {
+      partialContractTerms: {
+        amount: args.amount,
+        summary: args.summary ?? "Test P2P Payment",
+        purse_expiration,
+      },
+    },
+  );
+
+  await args.walletClient.call(WalletApiOperation.TestingWaitTransactionState, 
{
+    transactionId: initRet.transactionId,
+    txState: {
+      major: TransactionMajorState.Pending,
+      minor: TransactionMinorState.Ready,
+    },
+  });
+
+  const txDet = await args.walletClient.call(
+    WalletApiOperation.GetTransactionById,
+    {
+      transactionId: initRet.transactionId,
+    },
+  );
+
+  t.assertTrue(txDet.type === TransactionType.PeerPullCredit);
+  const talerUri = txDet.talerUri;
+  t.assertTrue(!!talerUri);
+
+  return {
+    transactionId: initRet.transactionId,
+    talerUri,
+  };
+}
+
+runKycPeerPullTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index d50b3e1ad..553873f1a 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -48,6 +48,7 @@ import { runExchangeTimetravelTest } from 
"./test-exchange-timetravel.js";
 import { runFeeRegressionTest } from "./test-fee-regression.js";
 import { runForcedSelectionTest } from "./test-forced-selection.js";
 import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js";
+import { runKycPeerPullTest } from "./test-kyc-peer-pull.js";
 import { runKycPeerPushTest } from "./test-kyc-peer-push.js";
 import { runKycThresholdWithdrawalTest } from 
"./test-kyc-threshold-withdrawal.js";
 import { runKycTest } from "./test-kyc.js";
@@ -250,6 +251,7 @@ const allTests: TestMainFunction[] = [
   runKycThresholdWithdrawalTest,
   runKycExchangeWalletTest,
   runKycPeerPushTest,
+  runKycPeerPullTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
index 7071010b8..8bda9e1e6 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -50,7 +50,6 @@ import {
   encodeCrock,
   getRandomBytes,
   j2s,
-  makeErrorDetail,
   stringifyPayPullUri,
   stringifyTalerUri,
   talerPaytoFromExchangeReserve,
@@ -61,7 +60,6 @@ import {
   TaskIdStr,
   TaskIdentifiers,
   TaskRunResult,
-  TaskRunResultType,
   TombstoneTag,
   TransactionContext,
   constructTaskIdentifier,
@@ -70,7 +68,6 @@ import {
 } from "./common.js";
 import {
   KycPendingInfo,
-  KycUserType,
   OperationRetryRecord,
   PeerPullCreditRecord,
   PeerPullPaymentCreditStatus,
@@ -641,17 +638,19 @@ async function longpollKycStatus(
   pursePub: string,
   exchangeUrl: string,
   kycInfo: KycPendingInfo,
-  userType: KycUserType,
 ): Promise<TaskRunResult> {
-  const transactionId = constructTransactionIdentifier({
-    tag: TransactionType.PeerPullCredit,
-    pursePub,
+  // FIXME: What if this changes? Should be part of the p2p record
+  const mergeReserveInfo = await getMergeReserveInfo(wex, {
+    exchangeBaseUrl: exchangeUrl,
   });
+
+  const sigResp = await wex.cryptoApi.signWalletKycAuth({
+    accountPriv: mergeReserveInfo.reservePriv,
+    accountPub: mergeReserveInfo.reservePub,
+  });
+
   const ctx = new PeerPullCreditTransactionContext(wex, pursePub);
-  const url = new URL(
-    `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
-    exchangeUrl,
-  );
+  const url = new URL(`kyc-check/${kycInfo.requirementRow}`, exchangeUrl);
   const kycStatusRes = await wex.ws.runLongpollQueueing(
     wex,
     url.hostname,
@@ -660,6 +659,9 @@ async function longpollKycStatus(
       logger.info(`kyc url ${url.href}`);
       return await wex.http.fetch(url.href, {
         method: "GET",
+        headers: {
+          ["Account-Owner-Signature"]: sigResp.sig,
+        },
         cancellationToken: wex.cancellationToken,
       });
     },
@@ -690,7 +692,7 @@ async function longpollKycStatus(
         return { oldTxState, newTxState };
       },
     );
-    notifyTransition(wex, transactionId, transitionInfo);
+    notifyTransition(wex, ctx.transactionId, transitionInfo);
     return TaskRunResult.progress();
   } else if (kycStatusRes.status === HttpStatusCode.Accepted) {
     return TaskRunResult.longpollReturnedPending();
@@ -969,7 +971,6 @@ export async function processPeerPullCredit(
         pursePub,
         pullIni.exchangeBaseUrl,
         pullIni.kycInfo,
-        "individual",
       );
     }
     case PeerPullPaymentCreditStatus.PendingCreatePurse:
@@ -1002,22 +1003,32 @@ async function processPeerPullCreditKycRequired(
   const ctx = new PeerPullCreditTransactionContext(wex, peerIni.pursePub);
   const { pursePub } = peerIni;
 
-  const userType = "individual";
+  // FIXME: What if this changes? Should be part of the p2p record
+  const mergeReserveInfo = await getMergeReserveInfo(wex, {
+    exchangeBaseUrl: peerIni.exchangeBaseUrl,
+  });
+
+  const sigResp = await wex.cryptoApi.signWalletKycAuth({
+    accountPriv: mergeReserveInfo.reservePriv,
+    accountPub: mergeReserveInfo.reservePub,
+  });
+
   const url = new URL(
-    
`kyc-check/${kycPending.requirement_row}/${kycPending.h_payto}/${userType}`,
+    `kyc-check/${kycPending.requirement_row}`,
     peerIni.exchangeBaseUrl,
   );
 
   logger.info(`kyc url ${url.href}`);
   const kycStatusRes = await wex.http.fetch(url.href, {
     method: "GET",
+    headers: {
+      ["Account-Owner-Signature"]: sigResp.sig,
+    },
     cancellationToken: wex.cancellationToken,
   });
 
   if (
     kycStatusRes.status === HttpStatusCode.Ok ||
-    //FIXME: NoContent is not expected 
https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge
-    // remove after the exchange is fixed or clarified
     kycStatusRes.status === HttpStatusCode.NoContent
   ) {
     logger.warn("kyc requested, but already fulfilled");
@@ -1045,20 +1056,9 @@ async function processPeerPullCreditKycRequired(
         const newTxState = computePeerPullCreditTransactionState(peerInc);
         await tx.peerPullCredit.put(peerInc);
         await ctx.updateTransactionMeta(tx);
-        // We'll remove this eventually!  New clients should rely on the
-        // kycUrl field of the transaction, not the error code.
-        const res: TaskRunResult = {
-          type: TaskRunResultType.Error,
-          errorDetail: makeErrorDetail(
-            TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
-            {
-              kycUrl: kycStatus.kyc_url,
-            },
-          ),
-        };
         return {
           transitionInfo: { oldTxState, newTxState },
-          result: res,
+          result: TaskRunResult.progress(),
         };
       },
     );

-- 
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]