/*
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
*/
/**
* Imports.
*/
import {
AbsoluteTime,
AmountString,
Duration,
j2s,
TalerCorebankApiClient,
TransactionIdStr,
TransactionMajorState,
TransactionMinorState,
TransactionType,
} from "@gnu-taler/taler-util";
import {
createSyncCryptoApi,
EddsaKeyPairStrings,
WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
import {
BankService,
DbInfo,
ExchangeService,
getTestHarnessPaytoForLabel,
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: EddsaKeyPairStrings;
}
async function createKycTestkudosEnvironment(
t: GlobalTestState,
coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
opts: EnvOptions = {},
): Promise {
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 = getTestHarnessPaytoForLabel(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 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.Pending,
minor: TransactionMinorState.Ready,
},
});
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.Done,
},
});
}
/**
* Initiate a pull credit 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.MergeKycRequired,
},
});
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"];