-more general KYC logic
This commit is contained in:
parent
266068c96c
commit
61f39f0941
@ -73,6 +73,11 @@ struct TEH_KycProvider
|
|||||||
*/
|
*/
|
||||||
struct TEH_KYCLOGIC_ProviderDetails *pd;
|
struct TEH_KYCLOGIC_ProviderDetails *pd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cost of running this provider's KYC.
|
||||||
|
*/
|
||||||
|
unsigned long long cost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the @e checks array.
|
* Length of the @e checks array.
|
||||||
*/
|
*/
|
||||||
@ -156,7 +161,7 @@ static unsigned int num_kyc_triggers;
|
|||||||
/**
|
/**
|
||||||
* Array of configured providers.
|
* Array of configured providers.
|
||||||
*/
|
*/
|
||||||
static struct TEH_KycProvider *kyc_providers;
|
static struct TEH_KycProvider **kyc_providers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the #kyc_providers array.
|
* Length of the #kyc_providers array.
|
||||||
@ -419,6 +424,7 @@ add_provider (const char *section)
|
|||||||
kp->provider_section_name = section;
|
kp->provider_section_name = section;
|
||||||
kp->user_type = ut;
|
kp->user_type = ut;
|
||||||
kp->logic = lp;
|
kp->logic = lp;
|
||||||
|
kp->cost = cost;
|
||||||
add_checks (checks,
|
add_checks (checks,
|
||||||
&kp->provided_checks,
|
&kp->provided_checks,
|
||||||
&kp->num_checks);
|
&kp->num_checks);
|
||||||
@ -574,6 +580,33 @@ handle_section (void *cls,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator for qsort. Compares two triggers
|
||||||
|
* by timeframe to sort triggers by time.
|
||||||
|
*
|
||||||
|
* @param p1 first trigger to compare
|
||||||
|
* @param p2 second trigger to compare
|
||||||
|
* @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
sort_by_timeframe (void *p1,
|
||||||
|
void *p2)
|
||||||
|
{
|
||||||
|
struct TEH_KycTrigger **t1 = p1;
|
||||||
|
struct TEH_KycTrigger **t2 = p2;
|
||||||
|
|
||||||
|
if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
|
||||||
|
<,
|
||||||
|
(*t2)->timeframe))
|
||||||
|
return -1;
|
||||||
|
if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
|
||||||
|
>,
|
||||||
|
(*t2)->timeframe))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
enum GNUNET_GenericReturnValue
|
enum GNUNET_GenericReturnValue
|
||||||
TEH_kyc_init (void)
|
TEH_kyc_init (void)
|
||||||
{
|
{
|
||||||
@ -599,7 +632,10 @@ TEH_kyc_init (void)
|
|||||||
TEH_kyc_done ();
|
TEH_kyc_done ();
|
||||||
return GNUNET_SYSERR;
|
return GNUNET_SYSERR;
|
||||||
}
|
}
|
||||||
|
qsort (kyc_triggers,
|
||||||
|
num_kyc_triggers,
|
||||||
|
sizeof (struct TEH_KycTrigger *),
|
||||||
|
&sort_by_timeframe);
|
||||||
return GNUNET_OK;
|
return GNUNET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,22 +690,288 @@ TEH_kyc_done (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for the #eval_trigger().
|
||||||
|
*/
|
||||||
|
struct ThresholdTestContext
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Total amount so far.
|
||||||
|
*/
|
||||||
|
struct TALER_Amount total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger event to evaluate triggers of.
|
||||||
|
*/
|
||||||
|
enum TEH_KycTriggerEvent event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offset in the triggers array where we need to start
|
||||||
|
* checking for triggers. All trigges below this
|
||||||
|
* offset were already hit.
|
||||||
|
*/
|
||||||
|
unsigned int start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of checks needed so far.
|
||||||
|
*/
|
||||||
|
struct TEH_KycCheck **needed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer to number of entries used in @a needed.
|
||||||
|
*/
|
||||||
|
unsigned int *needed_cnt;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called on each @a amount that was found to
|
||||||
|
* be relevant for a KYC check.
|
||||||
|
*
|
||||||
|
* @param cls closure to allow the KYC module to
|
||||||
|
* total up amounts and evaluate rules
|
||||||
|
* @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
|
||||||
|
eval_trigger (void *cls,
|
||||||
|
const struct TALER_Amount *amount,
|
||||||
|
struct GNUNET_TIME_Absolute date)
|
||||||
|
{
|
||||||
|
struct ThresholdTestContext *ttc = cls;
|
||||||
|
struct GNUNET_TIME_Relative duration;
|
||||||
|
bool bump = true;
|
||||||
|
|
||||||
|
duration = GNUNET_TIME_absolute_get_duration (date);
|
||||||
|
if (0 >
|
||||||
|
TALER_amount_add (&ttc->total,
|
||||||
|
&ttc->total,
|
||||||
|
amount))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycTrigger *kt = kyc_triggers[i];
|
||||||
|
|
||||||
|
if (event != kt->trigger)
|
||||||
|
continue;
|
||||||
|
timeframe = GNUNET_TIME_relative_max (timeframe,
|
||||||
|
kt->timeframe);
|
||||||
|
if (GNUNET_TIME_relative_cmp (kt->timeframe,
|
||||||
|
>,
|
||||||
|
duration))
|
||||||
|
{
|
||||||
|
if (bump)
|
||||||
|
ttc->start = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (-1 ==
|
||||||
|
TALER_amount_cmp (&ttc->total,
|
||||||
|
&kt->threshold))
|
||||||
|
{
|
||||||
|
if (bump)
|
||||||
|
ttc->start = i;
|
||||||
|
bump = false;
|
||||||
|
continue; /* amount too low to trigger */
|
||||||
|
}
|
||||||
|
/* add check to list of required checks, unless
|
||||||
|
already present... */
|
||||||
|
for (unsigned int j = 0; j<kt->num_checks; j++)
|
||||||
|
{
|
||||||
|
struct TEH_KycCheck *rc = kt->required_checks[j];
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
|
||||||
|
if (ttc->needed[k] == rc)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (! found)
|
||||||
|
{
|
||||||
|
ttc->needed[*ttc->needed_cnt] = rc;
|
||||||
|
(*ttc->needed_cnt)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bump)
|
||||||
|
return GNUNET_NO; /* we hit all possible triggers! */
|
||||||
|
return GNUNET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for the #remove_satisfied().
|
||||||
|
*/
|
||||||
|
struct RemoveContext
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of checks needed so far.
|
||||||
|
*/
|
||||||
|
struct TEH_KycCheck **needed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer to number of entries used in @a needed.
|
||||||
|
*/
|
||||||
|
unsigned int *needed_cnt;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all checks satisfied by @a provider_name from
|
||||||
|
* our list of checks.
|
||||||
|
*
|
||||||
|
* @param cls a `struct RemoveContext`
|
||||||
|
* @param provider_name section name of provider that was already run previously
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
remove_satisfied (void *cls,
|
||||||
|
const char *provider_name)
|
||||||
|
{
|
||||||
|
struct RemoveContext *rc = cls;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i<num_kyc_providers; i++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycProvider *kp = kyc_providers[i];
|
||||||
|
|
||||||
|
if (0 != strcasecmp (provider_name,
|
||||||
|
kp->provider_section_name))
|
||||||
|
continue;
|
||||||
|
for (unsigned int j = 0; j<kp->num_checks; j++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycCheck *kc = kp->provided_checks[j];
|
||||||
|
|
||||||
|
for (unsigned int k = 0; k<*rc->needed_cnt; k++)
|
||||||
|
if (kc == rc->needed[k])
|
||||||
|
{
|
||||||
|
rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
|
||||||
|
(*rc->needed_cnt)--;
|
||||||
|
if (0 == *rc->needed_cnt)
|
||||||
|
return; /* for sure finished */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
|
TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
|
||||||
const struct TALER_PaytoHashP *h_payto,
|
const struct TALER_PaytoHashP *h_payto,
|
||||||
TEH_KycAmountIterator ai,
|
TEH_KycAmountIterator ai,
|
||||||
void *cls)
|
void *cls)
|
||||||
{
|
{
|
||||||
// Check if event(s) may at all require KYC.
|
struct TEH_KycCheck *needed[num_kyc_checks];
|
||||||
// If so, check what provider checks are
|
unsigned int needed_cnt = 0;
|
||||||
// already satisfied for h_payto (with database)
|
struct GNUNET_TIME_Relative timeframe;
|
||||||
// If unsatisfied checks are left, use 'ai'
|
unsigned long long min_cost = ULONG_LONG_MAX;
|
||||||
// to check if amount is high enough to trigger them.
|
unsigned int max_checks = 0;
|
||||||
// If it is, find cheapest provider that satisfies
|
const struct TEH_KycProvider *kp_best = NULL;
|
||||||
// all of them (or, if multiple providers would be
|
|
||||||
// needed, return one of them).
|
timeframe = GNUNET_TIME_UNIT_ZERO;
|
||||||
GNUNET_break (0);
|
for (unsigned int i = 0; i<num_kyc_triggers; i++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycTrigger *kt = kyc_triggers[i];
|
||||||
|
|
||||||
|
if (event != kt->trigger)
|
||||||
|
continue;
|
||||||
|
timeframe = GNUNET_TIME_relative_max (timeframe,
|
||||||
|
kt->timeframe);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct GNUNET_TIME_Absolute now;
|
||||||
|
struct ThresholdTestContext ttc = {
|
||||||
|
.event = event,
|
||||||
|
.needed = needed,
|
||||||
|
.needed_cnt = &needed_cnt
|
||||||
|
};
|
||||||
|
|
||||||
|
TALER_amount_set_zero (TEH_currency,
|
||||||
|
&ttc.total);
|
||||||
|
now = GNUNET_TIME_absolute_get ();
|
||||||
|
ai (ai_cls,
|
||||||
|
GNUNET_TIME_absolute_subtract (now,
|
||||||
|
timeframe),
|
||||||
|
&eval_trigger,
|
||||||
|
&ttc);
|
||||||
|
}
|
||||||
|
if (0 == needed_cnt)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
{
|
||||||
|
struct RemoveContext rc = {
|
||||||
|
.needed = needed,
|
||||||
|
.needed_cnt = &needed_cnt
|
||||||
|
};
|
||||||
|
enum GNUNET_DB_QueryStatus qs;
|
||||||
|
|
||||||
|
/* Check what provider checks are already satisfied for h_payto (with
|
||||||
|
database), remove those from the 'needed' array. */
|
||||||
|
GNUNET_break (0);
|
||||||
|
qs = TEH_plugin->select_satisfied_kyc_processes (TEH_plugin->cls,
|
||||||
|
h_payto,
|
||||||
|
&remove_satisfied,
|
||||||
|
&rc);
|
||||||
|
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Count maximum number of remaining checks covered by any
|
||||||
|
provider */
|
||||||
|
for (unsigned int i = 0; i<num_kyc_providers; i++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycProvider *kp = kyc_providers[i];
|
||||||
|
unsigned int matched = 0;
|
||||||
|
|
||||||
|
for (unsigned int j = 0; j<kp->num_checks; j++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycCheck *kc = kp->provided_checks[j];
|
||||||
|
|
||||||
|
for (unsigned int k = 0; k<needed_cnt; k++)
|
||||||
|
if (kc == needed[k])
|
||||||
|
{
|
||||||
|
matched++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max_checks = GNUNET_MAX (max_checks,
|
||||||
|
matched);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find min-cost provider covering max_checks. */
|
||||||
|
for (unsigned int i = 0; i<num_kyc_providers; i++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycProvider *kp = kyc_providers[i];
|
||||||
|
unsigned int matched = 0;
|
||||||
|
|
||||||
|
for (unsigned int j = 0; j<kp->num_checks; j++)
|
||||||
|
{
|
||||||
|
const struct TEH_KycCheck *kc = kp->provided_checks[j];
|
||||||
|
|
||||||
|
for (unsigned int k = 0; k<needed_cnt; k++)
|
||||||
|
if (kc == needed[k])
|
||||||
|
{
|
||||||
|
matched++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( (max_checks == matched) &&
|
||||||
|
(kp->cost < min_cost) )
|
||||||
|
{
|
||||||
|
min_cost = kp->cost;
|
||||||
|
kp_best = kp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GNUNET_assert (NULL != kp_best);
|
||||||
|
return kp_best->provider_section_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,8 +203,7 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain the provider logic for a given
|
* Obtain the provider logic for a given @a provider_section_name.
|
||||||
* @a provider_section_name.
|
|
||||||
*
|
*
|
||||||
* @param provider_section_name identifies a KYC provider process
|
* @param provider_section_name identifies a KYC provider process
|
||||||
* @param[out] plugin set to the KYC logic API
|
* @param[out] plugin set to the KYC logic API
|
||||||
|
@ -865,6 +865,20 @@ typedef void
|
|||||||
const struct TALER_MasterSignatureP *master_sig);
|
const struct TALER_MasterSignatureP *master_sig);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called on all KYC process names that the given
|
||||||
|
* account has already passed.
|
||||||
|
*
|
||||||
|
* @param cls closure
|
||||||
|
* @param kyc_provider_section_name configuration section
|
||||||
|
* of the respective KYC process
|
||||||
|
*/
|
||||||
|
typedef void
|
||||||
|
(*TALER_EXCHANGEDB_SatisfiedProviderCallback)(
|
||||||
|
void *cls,
|
||||||
|
const char *kyc_provider_section_name);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called with information about the exchange's auditors.
|
* Function called with information about the exchange's auditors.
|
||||||
*
|
*
|
||||||
@ -5607,6 +5621,24 @@ struct TALER_EXCHANGEDB_Plugin
|
|||||||
uint64_t serial);
|
uint64_t serial);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call us on KYC processes satisfied for the given
|
||||||
|
* account.
|
||||||
|
*
|
||||||
|
* @param cls the @e cls of this struct with the plugin-specific state
|
||||||
|
* @param h_payto account identifier
|
||||||
|
* @param spc function to call for each satisfied KYC process
|
||||||
|
* @param spc_cls closure for @a spc
|
||||||
|
* @return transaction status code
|
||||||
|
*/
|
||||||
|
enum GNUNET_DB_QueryStatus
|
||||||
|
(*select_satisfied_kyc_processes)(
|
||||||
|
void *cls,
|
||||||
|
const struct TALER_PaytoHashP *h_payto,
|
||||||
|
TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
|
||||||
|
void *spc_cls);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _TALER_EXCHANGE_DB_H */
|
#endif /* _TALER_EXCHANGE_DB_H */
|
||||||
|
Loading…
Reference in New Issue
Block a user