commit f837ea49b9afb0997b5c287e3d0bc8fd98a1f265
parent 0c825ce4ee3c6e3ea40a15829f948e28b29df5fa
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 2 Dec 2024 22:30:28 +0100
refactor legi rule expiration logic for taler-exchange-aggregator, including split transaction to not make it spam AML program invocation
Diffstat:
11 files changed, 897 insertions(+), 412 deletions(-)
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
@@ -30,6 +30,10 @@
#include "taler_bank_service.h"
#include "taler_dbevents.h"
+/**
+ * How often do we retry after serialization failures?
+ */
+#define MAX_RETRIES 5
/**
* Information about one aggregation process to be executed. There is
@@ -108,9 +112,9 @@ struct AggregationUnit
struct Shard *shard;
/**
- * Currently active rule set.
+ * Handle to async process to obtain the legitimization rules.
*/
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+ struct TALER_EXCHANGEDB_RuleUpdater *ru;
/**
* Row in KYC table for legitimization requirements
@@ -119,6 +123,17 @@ struct AggregationUnit
uint64_t requirement_row;
/**
+ * How often did we retry the transaction?
+ */
+ unsigned int retries;
+
+ /**
+ * Should we run a follow-up transaction with a legitimization
+ * check?
+ */
+ bool legi_check;
+
+ /**
* Do we have an entry in the transient table for
* this aggregation?
*/
@@ -260,12 +275,133 @@ cleanup_au (struct AggregationUnit *au)
TALER_KYCLOGIC_run_aml_program_cancel (au->amlh);
au->amlh = NULL;
}
+ if (NULL != au->ru)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGEDB_update_rules_cancel (au->ru);
+ au->ru = NULL;
+ }
GNUNET_free (au->payto_uri.full_payto);
GNUNET_free (au);
}
/**
+ * Perform a database commit. If it fails, print a warning.
+ *
+ * @return status of commit
+ */
+static enum GNUNET_DB_QueryStatus
+commit_or_warn (void)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? GNUNET_ERROR_TYPE_INFO
+ : GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit database transaction!\n");
+ return qs;
+}
+
+
+/**
+ * Release lock on shard @a s in the database.
+ * On error, terminates this process.
+ *
+ * @param[in] s shard to free (and memory to release)
+ */
+static void
+release_shard (struct Shard *s)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db_plugin->release_revolving_shard (
+ db_plugin->cls,
+ "aggregator",
+ s->shard_start,
+ s->shard_end);
+ GNUNET_free (s);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Strange, but let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* normal case */
+ break;
+ }
+}
+
+
+/**
+ * Schedule the next major task, or exit depending on mode.
+ */
+static void
+next_task (uint64_t counter)
+{
+ if ( (GNUNET_YES == test_mode) &&
+ (0 == counter) )
+ {
+ /* in test mode, shutdown after a shard is done with 0 work */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No work done and in test mode, shutting down\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ /* If we ended up doing zero work, sleep a bit */
+ if (0 == counter)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Going to sleep for %s before trying again\n",
+ GNUNET_TIME_relative2s (aggregator_idle_sleep_interval,
+ true));
+ task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
+ &drain_kyc_alerts,
+ NULL);
+ }
+ else
+ {
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ }
+}
+
+
+/**
+ * Rollback the current transaction (if any),
+ * then free data stored in @a au, including @a au itself, and then
+ * run the next aggregation task.
+ *
+ * @param[in] au aggregation unit to clean up
+ */
+static void
+cleanup_and_next (struct AggregationUnit *au)
+{
+ struct Shard *s = au->shard;
+ uint64_t counter = (NULL == s) ? 0 : s->work_counter;
+
+ /* just in case, often no transaction is running here anymore */
+ db_plugin->rollback (db_plugin->cls);
+ cleanup_au (au);
+ if (NULL != s)
+ release_shard (s);
+ if (EXIT_SUCCESS == global_ret)
+ next_task (counter);
+}
+
+
+/**
* We're being aborted with CTRL-C (or SIGTERM). Shut down.
*
* @param cls closure
@@ -391,63 +527,6 @@ parse_aggregator_config (void)
/**
- * Perform a database commit. If it fails, print a warning.
- *
- * @return status of commit
- */
-static enum GNUNET_DB_QueryStatus
-commit_or_warn (void)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = db_plugin->commit (db_plugin->cls);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return qs;
- GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs)
- ? GNUNET_ERROR_TYPE_INFO
- : GNUNET_ERROR_TYPE_ERROR,
- "Failed to commit database transaction!\n");
- return qs;
-}
-
-
-/**
- * Release lock on shard @a s in the database.
- * On error, terminates this process.
- *
- * @param[in] s shard to free (and memory to release)
- */
-static void
-release_shard (struct Shard *s)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = db_plugin->release_revolving_shard (
- db_plugin->cls,
- "aggregator",
- s->shard_start,
- s->shard_end);
- GNUNET_free (s);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- GNUNET_break (0);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Strange, but let's just continue */
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* normal case */
- break;
- }
-}
-
-
-/**
* Callback to return all applicable amounts for the KYC
* decision to @ a cb.
*
@@ -543,6 +622,19 @@ rollback_aggregation (struct AggregationUnit *au)
/**
+ * Function called with legitimization rule set. Check
+ * how that affects the aggregation process.
+ *
+ * @param[in] cls a `struct AggregationUnit *`
+ * @param[in] rur new legitimization rule set to evaluate
+ */
+static void
+evaluate_rules (
+ void *cls,
+ struct TALER_EXCHANGEDB_RuleUpdaterResult *rur);
+
+
+/**
* The aggregation process succeeded and should be finally committed.
*
* @param[in] au aggregation that needs to be committed
@@ -550,14 +642,10 @@ rollback_aggregation (struct AggregationUnit *au)
static void
commit_aggregation (struct AggregationUnit *au)
{
- struct Shard *s = au->shard;
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Committing aggregation result over %s to %s\n",
TALER_amount2s (&au->final_amount),
au->payto_uri.full_payto);
- cleanup_au (au);
-
/* Now we can finally commit the overall transaction, as we are
again consistent if all of this passes. */
switch (commit_or_warn ())
@@ -566,79 +654,42 @@ commit_aggregation (struct AggregationUnit *au)
/* try again */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue on commit; trying again later!\n");
- run_task_with_shard (s);
+ cleanup_and_next (au);
return;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- db_plugin->rollback (db_plugin->cls); /* just in case */
- release_shard (s);
+ cleanup_and_next (au);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Commit complete, going again\n");
- run_task_with_shard (s);
+ if (au->legi_check)
+ {
+ au->legi_check = false;
+ au->ru = TALER_EXCHANGEDB_update_rules (
+ db_plugin,
+ &attribute_key,
+ &au->h_normalized_payto,
+ &evaluate_rules,
+ au);
+ if (NULL != au->ru)
+ return;
+ }
+ cleanup_and_next (au);
return;
default:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- db_plugin->rollback (db_plugin->cls); /* just in case */
- release_shard (s);
+ cleanup_and_next (au);
return;
}
}
/**
- * The aggregation process could not be concluded and its progress state
- * should be remembered in a transient aggregation.
- *
- * @param[in] au aggregation that needs to be committed
- * into a transient aggregation
- */
-static void
-commit_to_transient (struct AggregationUnit *au)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not ready for wire transfer (%s)\n",
- TALER_amount2s (&au->final_amount));
- if (au->have_transient)
- qs = db_plugin->update_aggregation_transient (db_plugin->cls,
- &au->h_full_payto,
- &au->wtid,
- au->requirement_row,
- &au->total_amount);
- else
- qs = db_plugin->create_aggregation_transient (db_plugin->cls,
- &au->h_full_payto,
- au->wa->section_name,
- &au->merchant_pub,
- &au->wtid,
- au->requirement_row,
- &au->total_amount);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Serialization issue, trying again later!\n");
- rollback_aggregation (au);
- return;
- }
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- fail_aggregation (au);
- return;
- }
- /* commit */
- commit_aggregation (au);
-}
-
-
-/**
* Trigger the wire transfer for the @a au
* and delete the record of the aggregation.
*
@@ -671,10 +722,16 @@ trigger_wire_transfer (struct AggregationUnit *au)
au->wa->method,
buf,
buf_size);
+ GNUNET_log (qs >= 0
+ ? GNUNET_ERROR_TYPE_DEBUG
+ : GNUNET_ERROR_TYPE_WARNING,
+ "wire_prepare_data_insert returned %d\n",
+ (int) qs);
GNUNET_free (buf);
}
/* Commit the WTID data to 'wire_out' */
if (qs >= 0)
+ {
qs = db_plugin->store_wire_transfer_out (
db_plugin->cls,
au->execution_time,
@@ -682,7 +739,12 @@ trigger_wire_transfer (struct AggregationUnit *au)
&au->h_full_payto,
au->wa->section_name,
&au->final_amount);
-
+ GNUNET_log (qs >= 0
+ ? GNUNET_ERROR_TYPE_DEBUG
+ : GNUNET_ERROR_TYPE_WARNING,
+ "store_wire_transfer_out returned %d\n",
+ (int) qs);
+ }
if ( (qs >= 0) &&
au->have_transient)
qs = db_plugin->delete_aggregation_transient (
@@ -721,240 +783,53 @@ trigger_wire_transfer (struct AggregationUnit *au)
}
-/**
- * Function called with legitimization rule set. Check
- * how that affects the aggregation process.
- *
- * @param[in] au active aggregation
- * @param[in] lrs legitimization rule set to evaluate, NULL for defaults
- */
static void
-evaluate_lrs (
- struct AggregationUnit *au,
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs);
-
-
-/**
- * Function called after AML program was run. Decides how to
- * continue with the aggregation based on the AML result.
- *
- * @param cls a `struct AggregationUnit *`
- * @param apr result of the AML program.
- */
-static void
-aml_result_callback (
+evaluate_rules (
void *cls,
- const struct TALER_KYCLOGIC_AmlProgramResult *apr);
-
-
-/**
- * Run the given measure @a m of the @a lrs for the
- * given aggregation process @a au.
- *
- * @param lrs a legitimization rule set containing @a m
- * @param m measure to run
- * @param au aggregation unit we are processing
- */
-static void
-run_measure (
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
- const struct TALER_KYCLOGIC_Measure *m,
- struct AggregationUnit *au)
-{
- if ( (NULL == m->check_name) ||
- (0 ==
- strcasecmp ("skip",
- m->check_name)) )
- {
- struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
- .account = &au->h_normalized_payto,
- .db_plugin = db_plugin,
- .attribute_key = &attribute_key
- };
-
- au->lrs = lrs;
- au->amlh = TALER_KYCLOGIC_run_aml_program3 (
- m,
- NULL /* no attributes */,
- &TALER_EXCHANGEDB_current_rule_builder,
- &hbc,
- &TALER_EXCHANGEDB_aml_history_builder,
- &hbc,
- &TALER_EXCHANGEDB_kyc_history_builder,
- &hbc,
- &aml_result_callback,
- au);
- return;
- }
- /* User MUST pass interactive check (odd): we cannot continue here */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Fallback measure %s involves check %s, blocking aggregation\n",
- m->measure_name,
- m->check_name);
- {
- /* persist measure */
- bool unknown_account;
- struct GNUNET_TIME_Timestamp last_date;
- json_t *succ_jmeasures = TALER_KYCLOGIC_get_jmeasures (
- lrs,
- m->measure_name);
- enum GNUNET_DB_QueryStatus qs;
-
- qs = db_plugin->insert_successor_measure (
- db_plugin->cls,
- &au->h_normalized_payto,
- GNUNET_TIME_timestamp_get (),
- m->measure_name,
- succ_jmeasures,
- &unknown_account,
- &last_date);
- json_decref (succ_jmeasures);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_log (
- GNUNET_ERROR_TYPE_INFO,
- "Serialization issue during aggregation; trying again later!\n");
- rollback_aggregation (au);
- return;
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- fail_aggregation (au);
- return;
- default:
- break;
- }
- }
- TALER_KYCLOGIC_rules_free (lrs);
- commit_to_transient (au);
-}
-
-
-/**
- * Function called after AML program was run.
- *
- * @param cls closure
- * @param apr result of the AML program.
- */
-static void
-aml_result_callback (
- void *cls,
- const struct TALER_KYCLOGIC_AmlProgramResult *apr)
+ struct TALER_EXCHANGEDB_RuleUpdaterResult *rur)
{
struct AggregationUnit *au = cls;
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = rur->lrs;
enum GNUNET_DB_QueryStatus qs;
+ const struct TALER_KYCLOGIC_KycRule *requirement;
- au->amlh = NULL;
- /* Update database update based on result */
- qs = TALER_EXCHANGEDB_persist_aml_program_result (
- db_plugin,
- 0, // FIXME: process row - #9303 may give us something?
- NULL /* no provider */,
- NULL /* no user ID */,
- NULL /* no legi ID */,
- NULL /* no attributes */,
- &attribute_key,
- 0 /* no birthday */,
- GNUNET_TIME_UNIT_FOREVER_ABS,
- &au->h_normalized_payto,
- apr);
- switch (qs)
+ au->ru = NULL;
+ if (TALER_EC_NONE != rur->ec)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- fail_aggregation (au);
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* Bad, couldn't persist AML result. Try again... */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Serialization issue persisting result of AML program. Restarting.\n");
- rollback_aggregation (au);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Strange, but let's just continue */
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* normal case */
- break;
- }
- switch (apr->status)
- {
- case TALER_KYCLOGIC_AMLR_SUCCESS:
+ if (NULL != lrs)
{
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
-
- TALER_KYCLOGIC_rules_free (au->lrs);
- au->lrs = NULL;
- lrs = TALER_KYCLOGIC_rules_parse (apr->details.success.new_rules);
- GNUNET_break (NULL != lrs);
- /* Fall back to default rules on parse error! */
- evaluate_lrs (au,
- lrs);
- return;
+ /* strange, but whatever */
+ TALER_KYCLOGIC_rules_free (lrs);
}
- case TALER_KYCLOGIC_AMLR_FAILURE:
+ /* Rollback just in case, should have already been done
+ before by the TALER_EXCHANGEDB_update_rules() logic. */
+ db_plugin->rollback (db_plugin->cls);
+ if ( (TALER_EC_GENERIC_DB_SOFT_FAILURE == rur->ec) &&
+ (au->retries++ < MAX_RETRIES) )
{
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = au->lrs;
- const char *fmn = apr->details.failure.fallback_measure;
- const struct TALER_KYCLOGIC_Measure *m;
-
- au->lrs = NULL;
- m = TALER_KYCLOGIC_get_measure (lrs,
- fmn);
- if (NULL == m)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fallback measure `%s' does not exist (anymore?).\n",
- fmn);
- TALER_KYCLOGIC_rules_free (lrs);
- trigger_wire_transfer (au);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization failure, trying again!\n");
+ au->ru = TALER_EXCHANGEDB_update_rules (
+ db_plugin,
+ &attribute_key,
+ &au->h_normalized_payto,
+ &evaluate_rules,
+ au);
+ if (NULL != au->ru)
return;
- }
- run_measure (lrs,
- m,
- au);
- return;
}
- }
- /* This should be impossible */
- GNUNET_assert (0);
-}
-
-
-/**
- * Function called with legitimization rule set. Check
- * how that affects the aggregation process.
- *
- * @param[in] au active aggregation
- * @param[in] lrs legitimization rule set to evaluate, NULL for defaults
- */
-static void
-evaluate_lrs (
- struct AggregationUnit *au,
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs)
-{
- enum GNUNET_DB_QueryStatus qs;
- const struct TALER_KYCLOGIC_KycRule *requirement;
-
- if ( (NULL != lrs) &&
- GNUNET_TIME_absolute_is_past
- (TALER_KYCLOGIC_rules_get_expiration (lrs).abs_time) )
- {
- const struct TALER_KYCLOGIC_Measure *m;
-
- m = TALER_KYCLOGIC_rules_get_successor (lrs);
- if (NULL != m)
- {
- run_measure (lrs,
- m,
- au);
- return;
- }
- /* fall back to default rules */
- TALER_KYCLOGIC_rules_free (lrs);
- lrs = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC rule evaluation failed hard: %s (%d, %s)\n",
+ TALER_ErrorCode_get_hint (rur->ec),
+ (int) rur->ec,
+ rur->hint);
+ cleanup_and_next (au);
+ return;
}
+ /* Note that here we are in an open transaction that fetched
+ (or updated) the current set of legitimization rules. So
+ we must properly commit at the end! */
{
struct TALER_Amount next_threshold;
@@ -970,11 +845,13 @@ evaluate_lrs (
{
TALER_KYCLOGIC_rules_free (lrs);
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- rollback_aggregation (au);
+ cleanup_and_next (au);
return;
}
if (NULL == requirement)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check clear, proceeding with wire transfer\n");
TALER_KYCLOGIC_rules_free (lrs);
trigger_wire_transfer (au);
return;
@@ -1001,17 +878,70 @@ evaluate_lrs (
}
if (qs < 0)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to persist KYC requirement `%s' in DB!\n",
TALER_KYCLOGIC_rule2s (requirement));
- rollback_aggregation (au);
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ global_ret = EXIT_FAILURE;
+ cleanup_and_next (au);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Legitimization process %llu started\n",
(unsigned long long) au->requirement_row);
TALER_KYCLOGIC_rules_free (lrs);
- commit_to_transient (au);
+ /* First commit, turns the rollback in cleanup into a NOP! */
+ commit_or_warn ();
+ cleanup_and_next (au);
+}
+
+
+/**
+ * The aggregation process could not be concluded and its progress state
+ * should be remembered in a transient aggregation.
+ *
+ * @param[in] au aggregation that needs to be committed
+ * into a transient aggregation
+ */
+static void
+commit_to_transient (struct AggregationUnit *au)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not ready for wire transfer (%s)\n",
+ TALER_amount2s (&au->final_amount));
+ if (au->have_transient)
+ qs = db_plugin->update_aggregation_transient (db_plugin->cls,
+ &au->h_full_payto,
+ &au->wtid,
+ au->requirement_row,
+ &au->total_amount);
+ else
+ qs = db_plugin->create_aggregation_transient (db_plugin->cls,
+ &au->h_full_payto,
+ au->wa->section_name,
+ &au->merchant_pub,
+ &au->wtid,
+ au->requirement_row,
+ &au->total_amount);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue, trying again later!\n");
+ rollback_aggregation (au);
+ return;
+ }
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ fail_aggregation (au);
+ return;
+ }
+ au->have_transient = true;
+ /* commit */
+ commit_aggregation (au);
}
@@ -1023,9 +953,6 @@ evaluate_lrs (
static void
check_legitimization_satisfied (struct AggregationUnit *au)
{
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
- enum GNUNET_DB_QueryStatus qs;
-
if (kyc_off)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1033,28 +960,10 @@ check_legitimization_satisfied (struct AggregationUnit *au)
trigger_wire_transfer (au);
return;
}
- {
- json_t *jrules;
-
- qs = db_plugin->get_kyc_rules2 (db_plugin->cls,
- &au->h_normalized_payto,
- &jrules);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- rollback_aggregation (au);
- return;
- }
- if (qs > 0)
- {
- lrs = TALER_KYCLOGIC_rules_parse (jrules);
- GNUNET_break (NULL != lrs);
- /* Fall back to default rules on parse error! */
- json_decref (jrules);
- }
- }
- evaluate_lrs (au,
- lrs);
+ /* get legi rules *after* committing, as the legi check
+ should run in a separate transaction! */
+ au->legi_check = true;
+ commit_to_transient (au);
}
@@ -1230,13 +1139,11 @@ run_aggregation (void *cls)
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
- cleanup_au (au);
- db_plugin->rollback (db_plugin->cls);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to begin deposit iteration!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
+ cleanup_and_next (au);
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
cleanup_au (au);
@@ -1245,46 +1152,17 @@ run_aggregation (void *cls)
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
{
- uint64_t counter = s->work_counter;
struct GNUNET_TIME_Relative duration
= GNUNET_TIME_absolute_get_duration (s->start_time.abs_time);
- cleanup_au (au);
- db_plugin->rollback (db_plugin->cls);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Completed shard [%u,%u] after %s with %llu deposits\n",
(unsigned int) s->shard_start,
(unsigned int) s->shard_end,
GNUNET_TIME_relative2s (duration,
true),
- (unsigned long long) counter);
- release_shard (s);
- if ( (GNUNET_YES == test_mode) &&
- (0 == counter) )
- {
- /* in test mode, shutdown after a shard is done with 0 work */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No work done and in test mode, shutting down\n");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- GNUNET_assert (NULL == task);
- /* If we ended up doing zero work, sleep a bit */
- if (0 == counter)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Going to sleep for %s before trying again\n",
- GNUNET_TIME_relative2s (aggregator_idle_sleep_interval,
- true));
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &drain_kyc_alerts,
- NULL);
- }
- else
- {
- task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
- NULL);
- }
+ (unsigned long long) s->work_counter);
+ cleanup_and_next (au);
return;
}
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
diff --git a/src/exchange/taler-exchange-httpd_kyc-info.c b/src/exchange/taler-exchange-httpd_kyc-info.c
@@ -648,11 +648,12 @@ TEH_handler_kyc_info (
TALER_KYCLOGIC_rules_free (lrs);
lrs = NULL;
}
- else if (0 == strcmp (successor_measure->prog_name, "SKIP"))
+ else if (0 == strcmp (successor_measure->prog_name,
+ "SKIP"))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Running successor measure %s.\n", successor_measure->
- measure_name);
+ "Running successor measure %s.\n",
+ successor_measure->measure_name);
/* FIXME(fdold, 2024-01-08): Consider limiting how
often we try this, in case we run into expired rulesets
repeatedly. */
diff --git a/src/exchangedb/exchangedb_aml.c b/src/exchangedb/exchangedb_aml.c
@@ -24,6 +24,13 @@
#include "taler_json_lib.h"
#include <gnunet/gnunet_common.h>
+/**
+ * Maximum recursion depth we allow for AML programs.
+ * Basically, after this number of "skip" processes
+ * we forcefully terminate the recursion and fail hard.
+ */
+#define MAX_DEPTH 16
+
enum GNUNET_DB_QueryStatus
TALER_EXCHANGEDB_persist_aml_program_result (
@@ -94,3 +101,506 @@ TALER_EXCHANGEDB_persist_aml_program_result (
GNUNET_assert (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
+
+struct TALER_EXCHANGEDB_RuleUpdater
+{
+ /**
+ * database plugin to use
+ */
+ struct TALER_EXCHANGEDB_Plugin *plugin;
+
+ /**
+ * key to use to decrypt attributes
+ */
+ struct TALER_AttributeEncryptionKeyP attribute_key;
+
+ /**
+ * account to get the rule set for
+ */
+ struct TALER_NormalizedPaytoHashP account;
+
+ /**
+ * function to call with the result
+ */
+ TALER_EXCHANGEDB_CurrentRulesCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Current rule set we are working on.
+ */
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+
+ /**
+ * Task for asynchronous continuations.
+ */
+ struct GNUNET_SCHEDULER_Task *t;
+
+ /**
+ * Handle to running AML program.
+ */
+ struct TALER_KYCLOGIC_AmlProgramRunnerHandle *amlh;
+
+ /**
+ * Error hint to return with @e ec.
+ */
+ const char *hint;
+
+ /**
+ * Row the rule set in @a lrs is based on.
+ */
+ uint64_t legitimization_outcome_last_row;
+
+ /**
+ * Taler error code to return.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Counter used to limit recursion depth.
+ */
+ unsigned int depth;
+};
+
+
+/**
+ * Function that finally returns the result to the application and
+ * cleans up.
+ *
+ * @param[in,out] cls a `struct TALER_EXCHANGEDB_RuleUpdater *`
+ */
+static void
+return_result (void *cls)
+{
+ struct TALER_EXCHANGEDB_RuleUpdater *ru = cls;
+ struct TALER_EXCHANGEDB_RuleUpdaterResult rur = {
+ .legitimization_outcome_last_row = ru->legitimization_outcome_last_row,
+ .lrs = ru->lrs,
+ .ec = ru->ec,
+ };
+
+ ru->t = NULL;
+ ru->cb (ru->cb_cls,
+ &rur);
+ ru->lrs = NULL;
+ TALER_EXCHANGEDB_update_rules_cancel (ru);
+}
+
+
+/**
+ * Finish the update returning the current lrs in @a ru.
+ *
+ * @param[in,out] ru account we are processing
+ */
+static void
+finish_update (struct TALER_EXCHANGEDB_RuleUpdater *ru)
+{
+ GNUNET_break (TALER_EC_NONE == ru->ec);
+ ru->t = GNUNET_SCHEDULER_add_now (&return_result,
+ ru);
+}
+
+
+/**
+ * Fail the update with the given @a ec and @a hint.
+ *
+ * @param[in,out] ru account we are processing
+ * @param ec error code to fail with
+ * @param hint hint to return, can be NULL
+ */
+static void
+fail_update (struct TALER_EXCHANGEDB_RuleUpdater *ru,
+ enum TALER_ErrorCode ec,
+ const char *hint)
+{
+ GNUNET_assert (NULL == ru->t);
+ ru->plugin->rollback (ru->plugin->cls);
+ ru->ec = ec;
+ ru->hint = hint;
+ ru->t = GNUNET_SCHEDULER_add_now (&return_result,
+ ru);
+}
+
+
+/**
+ * Check the rules in @a ru to see if they are current, and
+ * if not begin the updating process.
+ *
+ * @param[in] ru rule updater context
+ */
+static void
+check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru);
+
+
+/**
+ * Run the measure @a m in the context of the legitimisation rules
+ * of @a ru.
+ *
+ * @param ru updating context we are using
+ * @param m measure we need to run next
+ */
+static void
+run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru,
+ const struct TALER_KYCLOGIC_Measure *m);
+
+
+/**
+ * Function called after AML program was run.
+ *
+ * @param cls the `struct TALER_EXCHANGEDB_RuleUpdater *`
+ * @param apr result of the AML program.
+ */
+static void
+aml_result_callback (
+ void *cls,
+ const struct TALER_KYCLOGIC_AmlProgramResult *apr)
+{
+ struct TALER_EXCHANGEDB_RuleUpdater *ru = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_GenericReturnValue res;
+
+ ru->amlh = NULL;
+ res = ru->plugin->start (ru->plugin->cls,
+ "aml-persist-aml-program-result");
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "aml_result_callback");
+ return;
+ }
+ // FIXME: #9303 logic here?
+
+ /* Update database update based on result */
+ qs = TALER_EXCHANGEDB_persist_aml_program_result (
+ ru->plugin,
+ 0, // FIXME: process row - #9303 may give us something here!?
+ NULL /* no provider */,
+ NULL /* no user ID */,
+ NULL /* no legi ID */,
+ NULL /* no attributes */,
+ &ru->attribute_key,
+ 0 /* no birthday */,
+ GNUNET_TIME_UNIT_FOREVER_ABS,
+ &ru->account,
+ apr);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "persist_aml_program_result");
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* Bad, couldn't persist AML result. Try again... */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Serialization issue persisting result of AML program. Restarting.\n");
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "persist_aml_program_result");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Strange, but let's just continue */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* normal case */
+ break;
+ }
+ switch (apr->status)
+ {
+ case TALER_KYCLOGIC_AMLR_SUCCESS:
+ TALER_KYCLOGIC_rules_free (ru->lrs);
+ ru->lrs = NULL;
+ ru->lrs = TALER_KYCLOGIC_rules_parse (apr->details.success.new_rules);
+ /* Fall back to default rules on parse error! */
+ GNUNET_break (NULL != ru->lrs);
+ check_rules (ru);
+ return;
+ case TALER_KYCLOGIC_AMLR_FAILURE:
+ {
+ const char *fmn = apr->details.failure.fallback_measure;
+ const struct TALER_KYCLOGIC_Measure *m;
+
+ m = TALER_KYCLOGIC_get_measure (ru->lrs,
+ fmn);
+ if (NULL == m)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Fallback measure `%s' does not exist (anymore?).\n",
+ fmn);
+ TALER_KYCLOGIC_rules_free (ru->lrs);
+ ru->lrs = NULL;
+ finish_update (ru);
+ return;
+ }
+ run_measure (ru,
+ m);
+ return;
+ }
+ }
+ /* This should be impossible */
+ GNUNET_assert (0);
+}
+
+
+static void
+run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru,
+ const struct TALER_KYCLOGIC_Measure *m)
+{
+ if (NULL == m)
+ {
+ /* fall back to default rules */
+ TALER_KYCLOGIC_rules_free (ru->lrs);
+ ru->lrs = NULL;
+ finish_update (ru);
+ return;
+ }
+ ru->depth++;
+ if (ru->depth > MAX_DEPTH)
+ {
+ fail_update (ru,
+ TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED,
+ NULL);
+ return;
+ }
+ if ( (NULL == m->check_name) ||
+ (0 ==
+ strcasecmp ("skip",
+ m->check_name)) )
+ {
+ struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
+ .account = &ru->account,
+ .db_plugin = ru->plugin,
+ .attribute_key = &ru->attribute_key
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ // FIXME: #9303 logic here?
+ qs = ru->plugin->commit (ru->plugin->cls);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ fail_update (ru,
+ GNUNET_DB_STATUS_SOFT_ERROR == qs
+ ? TALER_EC_GENERIC_DB_SOFT_FAILURE
+ : TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "current-aml-rule-fetch");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Check is of type 'skip', running AML program %s.\n",
+ m->prog_name);
+ ru->amlh = TALER_KYCLOGIC_run_aml_program3 (
+ m,
+ NULL /* no attributes */,
+ &TALER_EXCHANGEDB_current_rule_builder,
+ &hbc,
+ &TALER_EXCHANGEDB_aml_history_builder,
+ &hbc,
+ &TALER_EXCHANGEDB_kyc_history_builder,
+ &hbc,
+ &aml_result_callback,
+ ru);
+ return;
+ }
+
+ /* User MUST pass interactive check */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Measure %s involves check %s\n",
+ m->measure_name,
+ m->check_name);
+ {
+ /* activate the measure/check */
+ json_t *succ_jmeasures
+ = TALER_KYCLOGIC_get_jmeasures (
+ ru->lrs,
+ m->measure_name);
+ bool unknown_account;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = ru->plugin->insert_successor_measure (
+ ru->plugin->cls,
+ &ru->account,
+ GNUNET_TIME_timestamp_get (),
+ m->measure_name,
+ succ_jmeasures,
+ &unknown_account,
+ &last_date);
+ json_decref (succ_jmeasures);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue!\n");
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "insert_successor_measure");
+ return;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_successor_measure");
+ return;
+ default:
+ break;
+ }
+ }
+ /* The rules remain these rules until the user passes the check */
+ finish_update (ru);
+}
+
+
+/**
+ * Update the expired legitimization rules in @a ru, checking for
+ * expiration first.
+ *
+ * @param[in,out] ru account we are processing
+ */
+static void
+update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru)
+{
+ const struct TALER_KYCLOGIC_Measure *m;
+
+ GNUNET_assert (NULL != ru->lrs);
+ GNUNET_assert (GNUNET_TIME_absolute_is_past (
+ TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time));
+ m = TALER_KYCLOGIC_rules_get_successor (ru->lrs);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Successor measure is %s.\n",
+ m->measure_name);
+ run_measure (ru,
+ m);
+}
+
+
+static void
+check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru)
+{
+ ru->depth++;
+ if (ru->depth > MAX_DEPTH)
+ {
+ fail_update (ru,
+ TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED,
+ NULL);
+ return;
+ }
+ if (NULL == ru->lrs)
+ {
+ /* return NULL, aka default rules */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Default rules apply\n");
+ finish_update (ru);
+ return;
+ }
+ if (! GNUNET_TIME_absolute_is_past
+ (TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time) )
+ {
+ /* Rules did not expire, return them! */
+ finish_update (ru);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Custom rules expired, updating...\n");
+ update_rules (ru);
+}
+
+
+/**
+ * Entrypoint that fetches the latest rules from the database
+ * and starts processing them.
+ *
+ * @param[in,out] ru account we are processing
+ */
+static void
+fetch_latest_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *jnew_rules;
+ enum GNUNET_GenericReturnValue res;
+
+ GNUNET_break (NULL == ru->lrs);
+ res = ru->plugin->start (ru->plugin->cls,
+ "aml-begin-lookup-rules-by-access-token");
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "aml_result_callback");
+ return;
+ }
+ qs = ru->plugin->lookup_rules_by_access_token (
+ ru->plugin->cls,
+ &ru->account,
+ &jnew_rules,
+ &ru->legitimization_outcome_last_row);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ fail_update (ru,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_rules_by_access_token");
+ return;
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+ (NULL != jnew_rules) )
+ {
+ ru->lrs = TALER_KYCLOGIC_rules_parse (jnew_rules);
+ GNUNET_break (NULL != ru->lrs);
+ json_decref (jnew_rules);
+ }
+ check_rules (ru);
+}
+
+
+struct TALER_EXCHANGEDB_RuleUpdater *
+TALER_EXCHANGEDB_update_rules (
+ struct TALER_EXCHANGEDB_Plugin *plugin,
+ const struct TALER_AttributeEncryptionKeyP *attribute_key,
+ const struct TALER_NormalizedPaytoHashP *account,
+ TALER_EXCHANGEDB_CurrentRulesCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGEDB_RuleUpdater *ru;
+
+ ru = GNUNET_new (struct TALER_EXCHANGEDB_RuleUpdater);
+ ru->plugin = plugin;
+ ru->attribute_key = *attribute_key;
+ ru->account = *account;
+ ru->cb = cb;
+ ru->cb_cls = cb_cls;
+ fetch_latest_rules (ru);
+ return ru;
+}
+
+
+void
+TALER_EXCHANGEDB_update_rules_cancel (
+ struct TALER_EXCHANGEDB_RuleUpdater *ru)
+{
+ if (NULL != ru->t)
+ {
+ GNUNET_SCHEDULER_cancel (ru->t);
+ ru->t = NULL;
+ }
+ if (NULL != ru->amlh)
+ {
+ TALER_KYCLOGIC_run_aml_program_cancel (ru->amlh);
+ ru->amlh = NULL;
+ }
+ if (NULL != ru->lrs)
+ {
+ TALER_KYCLOGIC_rules_free (ru->lrs);
+ ru->lrs = NULL;
+ }
+ GNUNET_free (ru);
+}
diff --git a/src/exchangedb/pg_commit.c b/src/exchangedb/pg_commit.c
@@ -42,7 +42,7 @@ TEH_PG_commit (void *cls)
enum GNUNET_DB_QueryStatus qs;
GNUNET_break (NULL != pg->transaction_name);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Committing transaction `%s'\n",
pg->transaction_name);
PREPARE (pg,
diff --git a/src/exchangedb/pg_rollback.c b/src/exchangedb/pg_rollback.c
@@ -41,7 +41,7 @@ TEH_PG_rollback (void *cls)
"Skipping rollback, no transaction active\n");
return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Rolling back transaction\n");
GNUNET_break (GNUNET_OK ==
GNUNET_PQ_exec_statements (pg->conn,
diff --git a/src/exchangedb/pg_select_aggregation_transient.c b/src/exchangedb/pg_select_aggregation_transient.c
@@ -49,7 +49,7 @@ TEH_PG_select_aggregation_transient (
wtid),
GNUNET_PQ_result_spec_end
};
- /* Used in #postgres_select_aggregation_transient() */
+
PREPARE (pg,
"select_aggregation_transient",
"SELECT"
diff --git a/src/include/taler_exchangedb_lib.h b/src/include/taler_exchangedb_lib.h
@@ -260,6 +260,97 @@ TALER_EXCHANGEDB_current_rule_builder (void *cls);
/**
+ * Handle for helper logic that advances rules to the currently
+ * valid rule set.
+ */
+struct TALER_EXCHANGEDB_RuleUpdater;
+
+/**
+ * Main result returned in the
+ * #TALER_EXCHANGEDB_CurrentRulesCallback
+ */
+struct TALER_EXCHANGEDB_RuleUpdaterResult
+{
+ /**
+ * Row the rule set is based on.
+ */
+ uint64_t legitimization_outcome_last_row;
+
+ /**
+ * Current legitimization rule set, owned by callee. Will be NULL on error
+ * or for default rules. Will not contain skip rules and not be expired.
+ */
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+
+ /**
+ * Hint to return, if @e ec is not #TALER_EC_NONE. Can be NULL.
+ */
+ const char *hint;
+
+ /**
+ * Error code in case a problem was encountered
+ * when fetching or updating the legitimization rules.
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * Function called with the current rule set.
+ *
+ * @param cls closure
+ * @param legitimization_outcome_last_row row the rule set is based on
+ * @param rur includes legitimziation rule set that applies to the account
+ * (owned by callee, callee must free the lrs!)
+ */
+typedef void
+(*TALER_EXCHANGEDB_CurrentRulesCallback)(
+ void *cls,
+ struct TALER_EXCHANGEDB_RuleUpdaterResult *rur);
+
+
+/**
+ * Obtains the current rule set for an account and advances it
+ * to the rule set that should apply right now. Considers
+ * expiration of rules as well as "skip" measures. Runs
+ * AML programs as needed to advance the rule book to the currently
+ * valid state.
+ *
+ * On success, the result is returned in a (fresh) transaction
+ * that must be committed for the result to be valid. This should
+ * be used to ensure transactionality of the AML program result.
+ *
+ * This function should be called *outside* of any other transaction.
+ * Calling it while a transaction is already running risks aborting
+ * that transaction.
+ *
+ * @param plugin database plugin to use
+ * @param attribute_key key to use to decrypt attributes
+ * @param account account to get the rule set for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel the operation
+ */
+struct TALER_EXCHANGEDB_RuleUpdater *
+TALER_EXCHANGEDB_update_rules (
+ struct TALER_EXCHANGEDB_Plugin *plugin,
+ const struct TALER_AttributeEncryptionKeyP *attribute_key,
+ const struct TALER_NormalizedPaytoHashP *account,
+ TALER_EXCHANGEDB_CurrentRulesCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel operation to get the current legitimization rule set.
+ *
+ * @param[in] ru operation to cancel
+ */
+void
+TALER_EXCHANGEDB_update_rules_cancel (
+ struct TALER_EXCHANGEDB_RuleUpdater *ru);
+
+
+/**
* Persist the given @a apr for the given process and account
* into the database via @a plugin.
*
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -2535,7 +2535,7 @@ add_program (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
}
}
-
+ GNUNET_free (required_inputs);
{
struct TALER_KYCLOGIC_AmlProgram *ap;
diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c
@@ -309,7 +309,7 @@ struct TALER_EXCHANGE_AgeWithdrawHandle
void *callback_cls;
/* The Handler for the actual call to the exchange */
- struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle;
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *protocol_handle;
};
/**
@@ -469,6 +469,7 @@ handle_reserve_age_withdraw_blinded_finished (
awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
+ break;
}
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API
@@ -695,6 +696,7 @@ copy_results (
},
};
+ awh->protocol_handle = NULL;
for (size_t n = 0; n< awh->num_coins; n++)
{
details[n] = awh->coin_data[n].coin_candidates[k].details;
@@ -704,6 +706,7 @@ copy_results (
awh->callback (awh->callback_cls,
&resp);
awh->callback = NULL;
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
}
@@ -727,7 +730,7 @@ call_age_withdraw_blinded (
awh->coin_data[n].planchet_details[k];
}
- awh->procotol_handle =
+ awh->protocol_handle =
TALER_EXCHANGE_age_withdraw_blinded (
awh->curl_ctx,
awh->keys,
@@ -736,7 +739,7 @@ call_age_withdraw_blinded (
awh->max_age,
awh->num_coins,
blinded_input,
- copy_results,
+ ©_results,
awh);
}
@@ -1064,8 +1067,8 @@ TALER_EXCHANGE_age_withdraw_cancel (
}
GNUNET_free (awh->coin_data);
TALER_EXCHANGE_keys_decref (awh->keys);
- TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle);
- awh->procotol_handle = NULL;
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->protocol_handle);
+ awh->protocol_handle = NULL;
GNUNET_free (awh);
}
diff --git a/src/testing/testing_api_cmd_coin_history.c b/src/testing/testing_api_cmd_coin_history.c
@@ -501,6 +501,7 @@ history_run (void *cls,
create_coin
= TALER_TESTING_interpreter_lookup_command (is,
cref);
+ GNUNET_free (cref);
if (NULL == create_coin)
{
GNUNET_break (0);
diff --git a/src/testing/testing_api_cmd_take_aml_decision.c b/src/testing/testing_api_cmd_take_aml_decision.c
@@ -413,6 +413,7 @@ take_aml_decision_cleanup (void *cls,
ds->dh = NULL;
}
json_decref (ds->new_rules);
+ json_decref (ds->properties);
GNUNET_free (ds);
}