commit d9c410a0ce8562032612952babab26302ac770a9
parent 4755837dcb817e100657aa1e7d189a8358166f36
Author: Florian Dold <florian@dold.me>
Date: Mon, 19 Aug 2024 19:09:22 +0200
wallet-core: update KYC implementation for deposit (incomplete!), add test
Diffstat:
4 files changed, 267 insertions(+), 11 deletions(-)
diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts
@@ -759,6 +759,7 @@ export async function createFaultInjectedMerchantTestkudosEnvironment(
}
export interface WithdrawViaBankResult {
+ accountPaytoUri: string;
withdrawalFinishedCond: Promise<true>;
}
@@ -816,6 +817,7 @@ export async function withdrawViaBankV2(
});
return {
+ accountPaytoUri: user.accountPaytoUri,
withdrawalFinishedCond,
};
}
@@ -880,6 +882,7 @@ export async function withdrawViaBankV3(
});
return {
+ accountPaytoUri: user.accountPaytoUri,
withdrawalFinishedCond,
};
}
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
@@ -0,0 +1,256 @@
+/*
+ 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 {
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+} 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 { EnvOptions, 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", "aggregate");
+ 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 runKycDepositAggregateTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const depositResp = await walletClient.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:10",
+ depositPaytoUri: wres.accountPaytoUri,
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Track,
+ },
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
+ await exchange.runTransferOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+}
+
+runKycDepositAggregateTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -47,6 +47,7 @@ import { runExchangePurseTest } from "./test-exchange-purse.js";
import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
import { runFeeRegressionTest } from "./test-fee-regression.js";
import { runForcedSelectionTest } from "./test-forced-selection.js";
+import { runKycDepositAggregateTest } from "./test-kyc-deposit-aggregate.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";
@@ -252,6 +253,7 @@ const allTests: TestMainFunction[] = [
runKycExchangeWalletTest,
runKycPeerPushTest,
runKycPeerPullTest,
+ runKycDepositAggregateTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts
@@ -43,7 +43,6 @@ import {
PrepareDepositRequest,
PrepareDepositResponse,
RefreshReason,
- ScopeInfo,
SelectedProspectiveCoin,
TalerError,
TalerErrorCode,
@@ -101,8 +100,6 @@ import {
timestampProtocolToDb,
} from "./db.js";
import {
- getExchangeScopeInfo,
- getExchangeScopeInfoOrUndefined,
getExchangeWireDetailsInTx,
getExchangeWireFee,
getScopeForAllExchanges,
@@ -200,7 +197,10 @@ export class DepositTransactionContext implements TransactionContext {
return {
type: TransactionType.Deposit,
txState,
- scopes: await getScopeForAllExchanges(tx, !dg.infoPerExchange? []: Object.keys(dg.infoPerExchange)),
+ scopes: await getScopeForAllExchanges(
+ tx,
+ !dg.infoPerExchange ? [] : Object.keys(dg.infoPerExchange),
+ ),
txActions: computeDepositTransactionActions(dg),
amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount),
amountEffective: isUnsuccessfulTransaction(txState)
@@ -773,14 +773,13 @@ async function processDepositGroupPendingKyc(
});
const kycInfo = depositGroup.kycInfo;
- const userType = "individual";
if (!kycInfo) {
throw Error("invalid DB state, in pending(kyc), but no kycInfo present");
}
const url = new URL(
- `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
+ `kyc-check/${kycInfo.requirementRow}`,
kycInfo.exchangeBaseUrl,
);
@@ -844,14 +843,10 @@ async function transitionToKycRequired(
exchangeUrl: string,
): Promise<TaskRunResult> {
const { depositGroupId } = depositGroup;
- const userType = "individual";
const ctx = new DepositTransactionContext(wex, depositGroupId);
- const url = new URL(
- `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
- exchangeUrl,
- );
+ const url = new URL(`kyc-check/${kycInfo.requirementRow}`, exchangeUrl);
logger.info(`kyc url ${url.href}`);
const kycStatusReq = await wex.http.fetch(url.href, {
method: "GET",