-more work on AML triggers for P2P transfers
This commit is contained in:
parent
174022907b
commit
6db4bdbe6e
@ -1 +1 @@
|
||||
Subproject commit 3a616a04f1cd946bf0641b54cd71f1b858174f74
|
||||
Subproject commit a7abaa856abbd16994132c5596ce04f442b9f4b9
|
@ -147,6 +147,13 @@ struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
|
||||
*/
|
||||
char *TEH_currency;
|
||||
|
||||
/**
|
||||
* What is the largest amount we allow a peer to
|
||||
* merge into a reserve before always triggering
|
||||
* an AML check?
|
||||
*/
|
||||
struct TALER_Amount TEH_aml_threshold;
|
||||
|
||||
/**
|
||||
* Our base URL.
|
||||
*/
|
||||
@ -1860,6 +1867,16 @@ exchange_serve_process_config (void)
|
||||
"CURRENCY");
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
TALER_config_get_amount (TEH_cfg,
|
||||
"taler",
|
||||
"AML_THRESHOLD",
|
||||
&TEH_aml_threshold))
|
||||
{
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||
"Need amount in section `TALER' under `AML_THRESHOLD'\n");
|
||||
return GNUNET_SYSERR;
|
||||
}
|
||||
if (GNUNET_OK !=
|
||||
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
|
||||
"exchange",
|
||||
|
@ -97,6 +97,13 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
|
||||
*/
|
||||
extern char *TEH_currency;
|
||||
|
||||
/**
|
||||
* What is the largest amount we allow a peer to
|
||||
* merge into a reserve before always triggering
|
||||
* an AML check?
|
||||
*/
|
||||
extern struct TALER_Amount TEH_aml_threshold;
|
||||
|
||||
/**
|
||||
* Our (externally visible) base URL.
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
This file is part of TALER
|
||||
Copyright (C) 2014-2022 Taler Systems SA
|
||||
Copyright (C) 2014-2023 Taler Systems SA
|
||||
|
||||
TALER is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
@ -108,6 +108,11 @@ struct BatchWithdrawContext
|
||||
*/
|
||||
unsigned int planchets_length;
|
||||
|
||||
/**
|
||||
* AML decision, #TALER_AML_NORMAL if we may proceed.
|
||||
*/
|
||||
enum TALER_AmlDecisionState aml_decision;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -150,6 +155,34 @@ batch_withdraw_amount_cb (void *cls,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called on each @a amount that was found to
|
||||
* be relevant for the AML check as it was merged into
|
||||
* the reserve.
|
||||
*
|
||||
* @param cls `struct TALER_Amount *` to total up the amounts
|
||||
* @param amount encountered transaction amount
|
||||
* @param date when was the amount encountered
|
||||
* @return #GNUNET_OK to continue to iterate,
|
||||
* #GNUNET_NO to abort iteration
|
||||
* #GNUNET_SYSERR on internal error (also abort itaration)
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
aml_amount_cb (
|
||||
void *cls,
|
||||
const struct TALER_Amount *amount,
|
||||
struct GNUNET_TIME_Absolute date)
|
||||
{
|
||||
struct TALER_Amount *total = cls;
|
||||
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_add (total,
|
||||
total,
|
||||
amount));
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function implementing withdraw transaction. Runs the
|
||||
* transaction logic; IF it returns a non-error code, the transaction
|
||||
@ -178,8 +211,102 @@ batch_withdraw_transaction (void *cls,
|
||||
bool balance_ok = false;
|
||||
bool found = false;
|
||||
const char *kyc_required;
|
||||
struct TALER_PaytoHashP reserve_h_payto;
|
||||
|
||||
wc->now = GNUNET_TIME_timestamp_get ();
|
||||
/* Do AML check: compute total merged amount and check
|
||||
against applicable AML threshold */
|
||||
{
|
||||
char *reserve_payto;
|
||||
|
||||
reserve_payto = TALER_reserve_make_payto (TEH_base_url,
|
||||
wc->reserve_pub);
|
||||
TALER_payto_hash (reserve_payto,
|
||||
&reserve_h_payto);
|
||||
GNUNET_free (reserve_payto);
|
||||
}
|
||||
{
|
||||
struct TALER_Amount merge_amount;
|
||||
struct TALER_Amount threshold;
|
||||
struct GNUNET_TIME_Absolute now_minus_one_month;
|
||||
|
||||
now_minus_one_month
|
||||
= GNUNET_TIME_absolute_subtract (wc->now.abs_time,
|
||||
GNUNET_TIME_UNIT_MONTHS);
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TEH_currency,
|
||||
&merge_amount));
|
||||
qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
now_minus_one_month,
|
||||
&aml_amount_cb,
|
||||
&merge_amount);
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"select_merge_amounts_for_kyc_check");
|
||||
return qs;
|
||||
}
|
||||
qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
&wc->aml_decision,
|
||||
&threshold);
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"select_aml_threshold");
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
threshold = TEH_aml_threshold; /* use default */
|
||||
wc->aml_decision = TALER_AML_NORMAL;
|
||||
}
|
||||
|
||||
switch (wc->aml_decision)
|
||||
{
|
||||
case TALER_AML_NORMAL:
|
||||
if (0 >= TALER_amount_cmp (&merge_amount,
|
||||
&threshold))
|
||||
{
|
||||
/* merge_amount <= threshold, continue withdraw below */
|
||||
break;
|
||||
}
|
||||
wc->aml_decision = TALER_AML_PENDING;
|
||||
qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
&merge_amount);
|
||||
if (qs <= 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"trigger_aml_process");
|
||||
return qs;
|
||||
}
|
||||
return qs;
|
||||
case TALER_AML_PENDING:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"AML already pending, doing nothing\n");
|
||||
return qs;
|
||||
case TALER_AML_FROZEN:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Account frozen, doing nothing\n");
|
||||
return qs;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the money came from a wire transfer */
|
||||
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
|
||||
wc->reserve_pub,
|
||||
&wc->h_payto);
|
||||
@ -352,6 +479,9 @@ generate_reply_success (const struct TEH_RequestContext *rc,
|
||||
&wc->h_payto,
|
||||
&wc->kyc);
|
||||
}
|
||||
if (TALER_AML_NORMAL != wc->aml_decision)
|
||||
return TEH_RESPONSE_reply_aml_blocked (rc->connection,
|
||||
wc->aml_decision);
|
||||
|
||||
sigs = json_array ();
|
||||
GNUNET_assert (NULL != sigs);
|
||||
|
@ -1142,4 +1142,29 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
|
||||
}
|
||||
|
||||
|
||||
MHD_RESULT
|
||||
TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
|
||||
enum TALER_AmlDecisionState status)
|
||||
{
|
||||
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case TALER_AML_NORMAL:
|
||||
GNUNET_break (0);
|
||||
return MHD_NO;
|
||||
case TALER_AML_PENDING:
|
||||
ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING;
|
||||
break;
|
||||
case TALER_AML_FROZEN:
|
||||
ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN;
|
||||
break;
|
||||
}
|
||||
return TALER_MHD_REPLY_JSON_PACK (
|
||||
connection,
|
||||
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
|
||||
TALER_JSON_pack_ec (ec));
|
||||
}
|
||||
|
||||
|
||||
/* end of taler-exchange-httpd_responses.c */
|
||||
|
@ -91,6 +91,19 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
|
||||
const struct TALER_EXCHANGEDB_KycStatus *kyc);
|
||||
|
||||
|
||||
/**
|
||||
* Send information that an AML process is blocking
|
||||
* the operation right now.
|
||||
*
|
||||
* @param connection connection to the client
|
||||
* @param status current AML status
|
||||
* @return MHD result code
|
||||
*/
|
||||
MHD_RESULT
|
||||
TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
|
||||
enum TALER_AmlDecisionState status);
|
||||
|
||||
|
||||
/**
|
||||
* Send assertion that the given denomination key hash
|
||||
* is not usable (typically expired) at this time.
|
||||
|
@ -61,16 +61,21 @@ struct WithdrawContext
|
||||
struct TALER_EXCHANGEDB_KycStatus kyc;
|
||||
|
||||
/**
|
||||
* Hash of the payto-URI representing the reserve
|
||||
* from which we are withdrawing.
|
||||
* Hash of the payto-URI representing the account
|
||||
* from which the money was put into the reserve.
|
||||
*/
|
||||
struct TALER_PaytoHashP h_payto;
|
||||
struct TALER_PaytoHashP h_account_payto;
|
||||
|
||||
/**
|
||||
* Current time for the DB transaction.
|
||||
*/
|
||||
struct GNUNET_TIME_Timestamp now;
|
||||
|
||||
/**
|
||||
* AML decision, #TALER_AML_NORMAL if we may proceed.
|
||||
*/
|
||||
enum TALER_AmlDecisionState aml_decision;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -108,7 +113,7 @@ withdraw_amount_cb (void *cls,
|
||||
return;
|
||||
qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
|
||||
TEH_plugin->cls,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_payto,
|
||||
limit,
|
||||
cb,
|
||||
cb_cls);
|
||||
@ -120,6 +125,34 @@ withdraw_amount_cb (void *cls,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called on each @a amount that was found to
|
||||
* be relevant for the AML check as it was merged into
|
||||
* the reserve.
|
||||
*
|
||||
* @param cls `struct TALER_Amount *` to total up the amounts
|
||||
* @param amount encountered transaction amount
|
||||
* @param date when was the amount encountered
|
||||
* @return #GNUNET_OK to continue to iterate,
|
||||
* #GNUNET_NO to abort iteration
|
||||
* #GNUNET_SYSERR on internal error (also abort itaration)
|
||||
*/
|
||||
static enum GNUNET_GenericReturnValue
|
||||
aml_amount_cb (
|
||||
void *cls,
|
||||
const struct TALER_Amount *amount,
|
||||
struct GNUNET_TIME_Absolute date)
|
||||
{
|
||||
struct TALER_Amount *total = cls;
|
||||
|
||||
GNUNET_assert (0 <=
|
||||
TALER_amount_add (total,
|
||||
total,
|
||||
amount));
|
||||
return GNUNET_OK;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function implementing withdraw transaction. Runs the
|
||||
* transaction logic; IF it returns a non-error code, the transaction
|
||||
@ -150,23 +183,116 @@ withdraw_transaction (void *cls,
|
||||
uint64_t ruuid;
|
||||
const struct TALER_CsNonce *nonce;
|
||||
const struct TALER_BlindedPlanchet *bp;
|
||||
struct TALER_PaytoHashP reserve_h_payto;
|
||||
|
||||
wc->now = GNUNET_TIME_timestamp_get ();
|
||||
/* Do AML check: compute total merged amount and check
|
||||
against applicable AML threshold */
|
||||
{
|
||||
char *reserve_payto;
|
||||
|
||||
reserve_payto = TALER_reserve_make_payto (TEH_base_url,
|
||||
&wc->collectable.reserve_pub);
|
||||
TALER_payto_hash (reserve_payto,
|
||||
&reserve_h_payto);
|
||||
GNUNET_free (reserve_payto);
|
||||
}
|
||||
{
|
||||
struct TALER_Amount merge_amount;
|
||||
struct TALER_Amount threshold;
|
||||
struct GNUNET_TIME_Absolute now_minus_one_month;
|
||||
|
||||
now_minus_one_month
|
||||
= GNUNET_TIME_absolute_subtract (wc->now.abs_time,
|
||||
GNUNET_TIME_UNIT_MONTHS);
|
||||
GNUNET_assert (GNUNET_OK ==
|
||||
TALER_amount_set_zero (TEH_currency,
|
||||
&merge_amount));
|
||||
qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
now_minus_one_month,
|
||||
&aml_amount_cb,
|
||||
&merge_amount);
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"select_merge_amounts_for_kyc_check");
|
||||
return qs;
|
||||
}
|
||||
qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
&wc->aml_decision,
|
||||
&threshold);
|
||||
if (qs < 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_FETCH_FAILED,
|
||||
"select_aml_threshold");
|
||||
return qs;
|
||||
}
|
||||
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
|
||||
{
|
||||
threshold = TEH_aml_threshold; /* use default */
|
||||
wc->aml_decision = TALER_AML_NORMAL;
|
||||
}
|
||||
|
||||
switch (wc->aml_decision)
|
||||
{
|
||||
case TALER_AML_NORMAL:
|
||||
if (0 >= TALER_amount_cmp (&merge_amount,
|
||||
&threshold))
|
||||
{
|
||||
/* merge_amount <= threshold, continue withdraw below */
|
||||
break;
|
||||
}
|
||||
wc->aml_decision = TALER_AML_PENDING;
|
||||
qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
|
||||
&reserve_h_payto,
|
||||
&merge_amount);
|
||||
if (qs <= 0)
|
||||
{
|
||||
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
|
||||
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
|
||||
*mhd_ret = TALER_MHD_reply_with_error (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
TALER_EC_GENERIC_DB_STORE_FAILED,
|
||||
"trigger_aml_process");
|
||||
return qs;
|
||||
}
|
||||
return qs;
|
||||
case TALER_AML_PENDING:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"AML already pending, doing nothing\n");
|
||||
return qs;
|
||||
case TALER_AML_FROZEN:
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Account frozen, doing nothing\n");
|
||||
return qs;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the money came from a wire transfer */
|
||||
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
|
||||
&wc->collectable.reserve_pub,
|
||||
&wc->h_payto);
|
||||
&wc->h_account_payto);
|
||||
if (qs < 0)
|
||||
return qs;
|
||||
/* If no results, reserve was created by merge,
|
||||
in which case no KYC check is required as the
|
||||
merge already did that. */
|
||||
/* If no results, reserve was created by merge, in which case no KYC check
|
||||
is required as the merge already did that. */
|
||||
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
|
||||
{
|
||||
const char *kyc_required;
|
||||
|
||||
qs = TALER_KYCLOGIC_kyc_test_required (
|
||||
TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_payto,
|
||||
TEH_plugin->select_satisfied_kyc_processes,
|
||||
TEH_plugin->cls,
|
||||
&withdraw_amount_cb,
|
||||
@ -191,7 +317,7 @@ withdraw_transaction (void *cls,
|
||||
return TEH_plugin->insert_kyc_requirement_for_account (
|
||||
TEH_plugin->cls,
|
||||
kyc_required,
|
||||
&wc->h_payto,
|
||||
&wc->h_account_payto,
|
||||
&wc->kyc.requirement_row);
|
||||
}
|
||||
}
|
||||
@ -515,8 +641,13 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
|
||||
|
||||
if (! wc.kyc.ok)
|
||||
return TEH_RESPONSE_reply_kyc_required (rc->connection,
|
||||
&wc.h_payto,
|
||||
&wc.h_account_payto,
|
||||
&wc.kyc);
|
||||
|
||||
if (TALER_AML_NORMAL != wc.aml_decision)
|
||||
return TEH_RESPONSE_reply_aml_blocked (rc->connection,
|
||||
wc.aml_decision);
|
||||
|
||||
{
|
||||
MHD_RESULT ret;
|
||||
|
||||
|
@ -7,6 +7,7 @@ TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
|
||||
# Currency supported by the exchange (can only be one)
|
||||
CURRENCY = EUR
|
||||
CURRENCY_ROUND_UNIT = EUR:0.01
|
||||
AML_THRESHOLD = EUR:1000000
|
||||
|
||||
[auditor]
|
||||
TINY_AMOUNT = EUR:0.01
|
||||
|
@ -54,25 +54,6 @@ compute_notify_on_reserve (const struct TALER_ReservePublicKeyP *reserve_pub)
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
notify_on_reserve (struct PostgresClosure *pg,
|
||||
const struct TALER_ReservePublicKeyP *reserve_pub)
|
||||
{
|
||||
struct TALER_ReserveEventP rep = {
|
||||
.header.size = htons (sizeof (rep)),
|
||||
.header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
|
||||
.reserve_pub = *reserve_pub
|
||||
};
|
||||
|
||||
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||
"Notifying on reserve!\n");
|
||||
TEH_PG_event_notify (pg,
|
||||
&rep.header,
|
||||
NULL,
|
||||
0);
|
||||
}
|
||||
|
||||
|
||||
static enum GNUNET_DB_QueryStatus
|
||||
insert1 (struct PostgresClosure *pg,
|
||||
const struct TALER_EXCHANGEDB_ReserveInInfo reserves[1],
|
||||
|
@ -41,7 +41,7 @@ TEH_PG_select_aml_threshold (
|
||||
uint32_t status32 = TALER_AML_NORMAL;
|
||||
struct GNUNET_PQ_ResultSpec rs[] = {
|
||||
TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
|
||||
&threshold),
|
||||
threshold),
|
||||
GNUNET_PQ_result_spec_uint32 ("status",
|
||||
&status32),
|
||||
GNUNET_PQ_result_spec_end
|
||||
|
Loading…
Reference in New Issue
Block a user