exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/exchange/taler-exchange-aggregator.c | 678++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/exchange/taler-exchange-httpd_kyc-info.c | 7++++---
Msrc/exchangedb/exchangedb_aml.c | 510+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchangedb/pg_commit.c | 2+-
Msrc/exchangedb/pg_rollback.c | 2+-
Msrc/exchangedb/pg_select_aggregation_transient.c | 2+-
Msrc/include/taler_exchangedb_lib.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kyclogic/kyclogic_api.c | 2+-
Msrc/lib/exchange_api_age_withdraw.c | 13++++++++-----
Msrc/testing/testing_api_cmd_coin_history.c | 1+
Msrc/testing/testing_api_cmd_take_aml_decision.c | 1+
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, + &copy_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); }