diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore index 59088a1b3..963062c1e 100644 --- a/src/auditor/.gitignore +++ b/src/auditor/.gitignore @@ -23,3 +23,4 @@ auditor-basedb.sqlite3 taler-auditor-test.sqlite3 libeufin-nexus.pid libeufin-sandbox.pid +taler-helper-auditor-purses diff --git a/src/auditor/taler-helper-auditor-purses.c b/src/auditor/taler-helper-auditor-purses.c index cfc345ebe..6a101ee44 100644 --- a/src/auditor/taler-helper-auditor-purses.c +++ b/src/auditor/taler-helper-auditor-purses.c @@ -68,6 +68,11 @@ static json_t *report_purse_balance_insufficient_inconsistencies; */ static struct TALER_Amount total_balance_insufficient_loss; +/** + * Total amount purse decisions are delayed past deadline. + */ +static struct TALER_Amount total_delayed_decisions; + /** * Array of reports about purses's not being closed inconsitencies. */ @@ -199,6 +204,54 @@ report_row_inconsistency (const char *table, } +/** + * Obtain the purse fee for a purse created at @a time. + * + * @param atime when was the purse created + * @param[out] fee set to the purse fee + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +get_purse_fee (struct GNUNET_TIME_Timestamp atime, + struct TALER_Amount *fee) +{ + struct TALER_MasterSignatureP master_sig; + struct GNUNET_TIME_Timestamp start_date; + struct GNUNET_TIME_Timestamp end_date; + struct TALER_GlobalFeeSet fees; + struct GNUNET_TIME_Relative ptimeout; + struct GNUNET_TIME_Relative ktimeout; + struct GNUNET_TIME_Relative hexp; + uint32_t pacl; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + TALER_ARL_edb->get_global_fee (TALER_ARL_edb->cls, + atime, + &start_date, + &end_date, + &fees, + &ptimeout, + &ktimeout, + &hexp, + &pacl, + &master_sig)) + { + char *diag; + + GNUNET_asprintf (&diag, + "purse fee unavailable at %s\n", + GNUNET_TIME_timestamp2s (atime)); + report_row_inconsistency ("purse-fee", + atime.abs_time.abs_value_us, + diag); + GNUNET_free (diag); + return GNUNET_SYSERR; + } + *fee = fees.purse; + return GNUNET_OK; +} + + /* ***************************** Analyze purses ************************ */ /* This logic checks the purses_requests, purse_deposits, purse_refunds, purse_merges and account_merges */ @@ -215,17 +268,37 @@ struct PurseSummary struct TALER_PurseContractPublicKeyP purse_pub; /** - * Balance of the purse from deposits (excludes - * deposit fees). - * Updated only in #handle_purse_deposits(). + * Balance of the purse from deposits (includes purse fee, excludes deposit + * fees), as calculated by auditor. */ struct TALER_Amount balance; /** - * Expected value of the purse. + * Expected value of the purse, excludes purse fee. */ struct TALER_Amount total_value; + /** + * Purse balance according to exchange DB. + */ + struct TALER_Amount exchange_balance; + + /** + * Contract terms of the purse. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Merge timestamp (as per exchange DB). + */ + struct GNUNET_TIME_Timestamp merge_timestamp; + + /** + * Purse creation date. This is when the merge + * fee is applied. + */ + struct GNUNET_TIME_Timestamp creation_date; + /** * Purse expiration date. */ @@ -328,6 +401,21 @@ setup_purse (struct PurseContext *pc, GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TALER_ARL_currency, &ps->balance)); + /* get purse meta-data from exchange DB */ + qs = TALER_ARL_edb->select_purse (TALER_ARL_edb->cls, + purse_pub, + &ps->creation_date, + &ps->expiration_date, + &ps->total_value, + &ps->exchange_balance, + &ps->h_contract_terms, + &ps->merge_timestamp); + if (0 >= qs) + { + GNUNET_free (ps); + pc->qs = qs; + return NULL; + } if (0 > (qs = load_auditor_purse_summary (ps))) { GNUNET_free (ps); @@ -344,33 +432,76 @@ setup_purse (struct PurseContext *pc, /** - * Check that the purse summary matches what the exchange database - * thinks about the purse, and update our own state of the purse. + * Function called on purse requests. * - * Remove all purses that we are happy with from the DB. - * - * @param cls our `struct PurseContext` - * @param key hash of the purse public key - * @param value a `struct PurseSummary` - * @return #GNUNET_OK to process more entries - */ + * @param cls closure + * @param purse_pub public key of the purse + * @param merge_pub public key representing the merge capability + * @param purse_expiration when would an unmerged purse expire + * @param h_contract_terms contract associated with the purse + * @param age_limit the age limit for deposits into the purse + * @param target_amount amount to be put into the purse + * @param purse_sig signature of the purse over the initialization data + * @return #GNUNET_OK to continue to iterate + */ static enum GNUNET_GenericReturnValue -verify_purse_balance (void *cls, - const struct GNUNET_HashCode *key, - void *value) +handle_purse_requested ( + void *cls, + uint64_t rowid, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + struct GNUNET_TIME_Timestamp purse_creation, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + uint32_t age_limit, + const struct TALER_Amount *target_amount, + const struct TALER_PurseContractSignatureP *purse_sig) { struct PurseContext *pc = cls; - struct PurseSummary *ps = value; - enum GNUNET_GenericReturnValue ret; + struct PurseSummary *ps; + struct GNUNET_HashCode key; - ret = GNUNET_OK; - // FIXME: implement! - GNUNET_assert (GNUNET_YES == - GNUNET_CONTAINER_multihashmap_remove (pc->purses, - key, - ps)); - GNUNET_free (ps); - return ret; + if (GNUNET_OK != + TALER_wallet_purse_create_verify (purse_expiration, + h_contract_terms, + merge_pub, + age_limit, + target_amount, + purse_pub, + purse_sig)) + { + TALER_ARL_report (report_bad_sig_losses, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("operation", + "purse-reqeust"), + GNUNET_JSON_pack_uint64 ("row", + rowid), + TALER_JSON_pack_amount ("loss", + target_amount), + GNUNET_JSON_pack_data_auto ("key_pub", + purse_pub))); + TALER_ARL_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + target_amount); + } + GNUNET_CRYPTO_hash (purse_pub, + sizeof (*purse_pub), + &key); + ps = GNUNET_new (struct PurseSummary); + ps->purse_pub = *purse_pub; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TALER_ARL_currency, + &ps->balance)); + ps->creation_date = purse_creation; + ps->expiration_date = purse_expiration; + ps->total_value = *target_amount; + ps->h_contract_terms = *h_contract_terms; + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (pc->purses, + &key, + ps, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + return GNUNET_OK; } @@ -401,14 +532,12 @@ handle_purse_deposits ( const struct TALER_DenominationPublicKey *denom_pub) { struct PurseContext *pc = cls; + struct TALER_Amount amount_minus_fee; + struct PurseSummary *ps; const char *base_url = (NULL == deposit->exchange_base_url) ? TALER_ARL_exchange_url : deposit->exchange_base_url; - enum GNUNET_DB_QueryStatus qs; - struct TALER_Amount amount_minus_fee; - struct TALER_Amount new_balance; - struct PurseSummary *rs; struct TALER_DenominationHashP h_denom_pub; /* should be monotonically increasing */ @@ -471,68 +600,42 @@ handle_purse_deposits ( return GNUNET_OK; } - TALER_ARL_amount_add (&new_balance, - auditor_balance, - &amount_minus_fee); - qs = TALER_ARL_edb->set_purse_balance (TALER_ARL_edb->cls, - &deposit->purse_pub, - &new_balance); - GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - pc->qs = qs; - return GNUNET_SYSERR; - } - if (TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE != - (flags & TALER_WAMF_MERGE_MODE_MASK)) - { - /* This just created the purse, actual credit to - the reserve will be done in handle_account_merged() */ - return GNUNET_OK; - } - if ( (NULL != deposit->exchange_base_url) && - (0 != strcmp (deposit->exchange_base_url, - TALER_ARL_exchange_url)) ) - { - /* credited reserve is at another exchange, do NOT credit here! */ - return GNUNET_OK; - } - - rs = setup_purse (pc, + ps = setup_purse (pc, &deposit->purse_pub); - if (NULL == rs) + if (NULL == ps) { - GNUNET_break (0); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs) + { + report_row_inconsistency ("purse-deposit", + rowid, + "purse not found"); + } + else + { + /* Database trouble!? */ + GNUNET_break (0); + } return GNUNET_SYSERR; } - if ( (-1 != TALER_amount_cmp (&new_balance, - purse_total)) && - (-1 == TALER_amount_cmp (auditor_balance, - purse_total)) ) - { - /* new balance at or above purse_total - (and previous balance was below); thus - credit reserve with purse value! */ - TALER_ARL_amount_add (&rs->balance, - &rs->balance, - purse_total); - } + TALER_ARL_amount_add (&ps->balance, + &ps->balance, + &amount_minus_fee); + TALER_ARL_amount_add (&balance.balance, + &balance.balance, + &amount_minus_fee); return GNUNET_OK; } /** - * Function called with details about purse - * merges that have been made, with + * Function called with details about purse merges that have been made, with * the goal of auditing the purse merge execution. * * @param cls closure * @param rowid unique serial ID for the deposit in our DB * @param partner_base_url where is the reserve, NULL for this exchange * @param amount total amount expected in the purse - * @param balance current balance in the purse (according to the auditor) + * @param balance current balance in the purse * @param flags purse flags * @param merge_pub merge capability key * @param reserve_pub reserve the merge affects @@ -557,97 +660,76 @@ handle_purse_merged ( { struct PurseContext *pc = cls; struct PurseSummary *ps; - char *reserve_url; /* should be monotonically increasing */ GNUNET_assert (rowid >= ppp.last_purse_merge_serial_id); ppp.last_purse_merge_serial_id = rowid + 1; - reserve_url - = TALER_reserve_make_payto (NULL == partner_base_url + + { + char *reserve_url; + + reserve_url + = TALER_reserve_make_payto (NULL == partner_base_url ? TALER_ARL_exchange_url : partner_base_url, - reserve_pub); - if (GNUNET_OK != - TALER_wallet_purse_merge_verify (reserve_url, - merge_timestamp, - purse_pub, - merge_pub, - merge_sig)) - { + reserve_pub); + if (GNUNET_OK != + TALER_wallet_purse_merge_verify (reserve_url, + merge_timestamp, + purse_pub, + merge_pub, + merge_sig)) + { + GNUNET_free (reserve_url); + TALER_ARL_report (report_bad_sig_losses, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("operation", + "merge-purse"), + GNUNET_JSON_pack_uint64 ("row", + rowid), + TALER_JSON_pack_amount ("loss", + amount), + GNUNET_JSON_pack_data_auto ("key_pub", + merge_pub))); + TALER_ARL_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount); + return GNUNET_OK; + } GNUNET_free (reserve_url); - TALER_ARL_report (report_bad_sig_losses, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("operation", - "merge-purse"), - GNUNET_JSON_pack_uint64 ("row", - rowid), - TALER_JSON_pack_amount ("loss", - amount), - GNUNET_JSON_pack_data_auto ("key_pub", - merge_pub))); - TALER_ARL_amount_add (&total_bad_sig_loss, - &total_bad_sig_loss, - amount); - return GNUNET_OK; - } - GNUNET_free (reserve_url); - if (TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE == - (flags & TALER_WAMF_MERGE_MODE_MASK)) - { - /* This just created the purse, actual credit to - the reserve will be done in handle_purse_deposits() */ - return GNUNET_OK; - } - if ( (NULL != partner_base_url) && - (0 != strcmp (partner_base_url, - TALER_ARL_exchange_url)) ) - { - /* credited reserve is at another exchange, do NOT credit here! */ - return GNUNET_OK; } + ps = setup_purse (pc, purse_pub); if (NULL == ps) { - GNUNET_break (0); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs) + { + report_row_inconsistency ("purse-merge", + rowid, + "purse not found"); + } + else + { + /* Database trouble!? */ + GNUNET_break (0); + } return GNUNET_SYSERR; } - if (-1 == TALER_amount_cmp (balance, - amount)) - { - struct TALER_Amount loss; - - TALER_ARL_amount_subtract (&loss, - amount, - balance); - /* illegal merge, balance is still below total purse value */ - TALER_ARL_report (report_purse_balance_insufficient_inconsistencies, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("operation", - "merge-purse"), - GNUNET_JSON_pack_uint64 ("row", - rowid), - TALER_JSON_pack_amount ("loss", - &loss), - GNUNET_JSON_pack_data_auto ("purse_pub", - purse_pub))); - TALER_ARL_amount_add (&total_balance_insufficient_loss, - &total_balance_insufficient_loss, - &loss); - return GNUNET_OK; - } + GNUNET_break (0 == + GNUNET_TIME_timestamp_cmp (merge_timestamp, + ==, + ps->merge_timestamp)); TALER_ARL_amount_add (&ps->balance, &ps->balance, amount); - // ps->a_expiration_date = FIXME: do we care? If so, set to what (so that the auditor no longer complains about the reserve not being closed) return GNUNET_OK; } /** - * Function called with details about - * account merge requests that have been made, with - * the goal of auditing the account merge execution. + * Function called with details about account merge requests that have been + * made, with the goal of auditing the account merge execution. * * @param cls closure * @param rowid unique serial ID for the deposit in our DB @@ -715,20 +797,265 @@ handle_account_merged ( purse_pub); if (NULL == ps) { - GNUNET_break (0); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs) + { + report_row_inconsistency ("account-merge", + rowid, + "purse not found"); + } + else + { + /* Database trouble!? */ + GNUNET_break (0); + } return GNUNET_SYSERR; } - TALER_ARL_amount_add (&balance.balance, &balance.balance, purse_fee); TALER_ARL_amount_add (&ps->balance, &ps->balance, - amount); + purse_fee); return GNUNET_OK; } +/** + * Function called with details about purse decisions that have been made. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param purse_pub which purse was the decision made on + * @param refunded true if decision was to refund + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static enum GNUNET_GenericReturnValue +handle_purse_decision ( + void *cls, + uint64_t rowid, + const struct TALER_PurseContractPublicKeyP *purse_pub, + bool refunded) +{ + struct PurseContext *pc = cls; + struct PurseSummary *ps; + struct GNUNET_HashCode key; + enum GNUNET_DB_QueryStatus qs; + struct TALER_Amount purse_fee; + struct TALER_Amount balance_without_purse_fee; + + ps = setup_purse (pc, + purse_pub); + if (NULL == ps) + { + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->qs) + { + report_row_inconsistency ("purse-decision", + rowid, + "purse not found"); + } + else + { + /* Database trouble!? */ + GNUNET_break (0); + } + return GNUNET_SYSERR; + } + if (GNUNET_OK != + get_purse_fee (ps->creation_date, + &purse_fee)) + { + report_row_inconsistency ("purse-request", + rowid, + "purse fee unavailable"); + } + if (0 > + TALER_amount_subtract (&balance_without_purse_fee, + &ps->balance, + &purse_fee)) + { + report_row_inconsistency ("purse-request", + rowid, + "purse fee higher than balance"); + TALER_amount_set_zero (TALER_ARL_currency, + &balance_without_purse_fee); + } + + if (refunded) + { + if (-1 != TALER_amount_cmp (&balance_without_purse_fee, + &ps->total_value)) + { + report_amount_arithmetic_inconsistency ("purse-decision: refund", + rowid, + &balance_without_purse_fee, + &ps->total_value, + 0); + } + } + else + { + if (-1 == TALER_amount_cmp (&balance_without_purse_fee, + &ps->total_value)) + { + report_amount_arithmetic_inconsistency ("purse-decision: merge", + rowid, + &ps->total_value, + &balance_without_purse_fee, + 0); + TALER_ARL_amount_add (&total_balance_insufficient_loss, + &total_balance_insufficient_loss, + &ps->total_value); + } + } + + qs = TALER_ARL_adb->delete_purse_info (TALER_ARL_adb->cls, + purse_pub, + &TALER_ARL_master_pub); + GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + pc->qs = qs; + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash (purse_pub, + sizeof (*purse_pub), + &key); + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (pc->purses, + &key, + ps)); + GNUNET_free (ps); + return GNUNET_OK; +} + + +/** + * Function called on expired purses. + * + * @param cls closure + * @param purse_pub public key of the purse + * @param balance amount of money in the purse + * @param expiration_date when did the purse expire? + * @return #GNUNET_OK to continue to iterate + */ +static enum GNUNET_GenericReturnValue +handle_purse_expired ( + void *cls, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_Amount *balance, + struct GNUNET_TIME_Timestamp expiration_date) +{ + struct PurseContext *pc = cls; + + (void) pc; + TALER_ARL_report (report_purse_not_closed_inconsistencies, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("purse_pub", + purse_pub), + TALER_JSON_pack_amount ("balance", + balance), + TALER_JSON_pack_time_abs_human ("expired", + expiration_date.abs_time))); + TALER_ARL_amount_add (&total_delayed_decisions, + &total_delayed_decisions, + balance); + return GNUNET_OK; +} + + +/** + * Check that the purse summary matches what the exchange database + * thinks about the purse, and update our own state of the purse. + * + * Remove all purses that we are happy with from the DB. + * + * @param cls our `struct PurseContext` + * @param key hash of the purse public key + * @param value a `struct PurseSummary` + * @return #GNUNET_OK to process more entries + */ +static enum GNUNET_GenericReturnValue +verify_purse_balance (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct PurseContext *pc = cls; + struct PurseSummary *ps = value; + enum GNUNET_GenericReturnValue ret; + enum GNUNET_DB_QueryStatus qs; + + ret = GNUNET_OK; + if (internal_checks) + { + struct TALER_Amount pf; + struct TALER_Amount balance_without_purse_fee; + + /* subtract purse fee from ps->balance to get actual balance we expect, as + we track the balance including purse fee, while the exchange subtracts + the purse fee early on. */ + if (GNUNET_OK != + get_purse_fee (ps->creation_date, + &pf)) + { + GNUNET_break (0); + report_row_inconsistency ("purse", + 0, + "purse fee unavailable"); + } + if (0 > + TALER_amount_subtract (&balance_without_purse_fee, + &ps->balance, + &pf)) + { + report_row_inconsistency ("purse", + 0, + "purse fee higher than balance"); + TALER_amount_set_zero (TALER_ARL_currency, + &balance_without_purse_fee); + } + + if (0 != TALER_amount_cmp (&ps->exchange_balance, + &balance_without_purse_fee)) + { + report_amount_arithmetic_inconsistency ("purse-balance", + 0, + &ps->exchange_balance, + &balance_without_purse_fee, + 0); + } + } + + if (ps->had_pi) + qs = TALER_ARL_adb->update_purse_info (TALER_ARL_adb->cls, + &ps->purse_pub, + &TALER_ARL_master_pub, + &ps->balance); + else + qs = TALER_ARL_adb->insert_purse_info (TALER_ARL_adb->cls, + &ps->purse_pub, + &TALER_ARL_master_pub, + &ps->balance, + ps->expiration_date); + GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + pc->qs = qs; + return GNUNET_SYSERR; + } + + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (pc->purses, + key, + ps)); + GNUNET_free (ps); + return ret; +} + + /** * Analyze purses for being well-formed. * @@ -762,9 +1089,10 @@ analyze_purses (void *cls) else { ppp_start = ppp; - // FIXME: add other values! GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming purse audit at %llu/%llu/%llu\n", + "Resuming purse audit at %llu/%llu/%llu/%llu/%llu\n", + (unsigned long long) ppp.last_purse_request_serial_id, + (unsigned long long) ppp.last_purse_decision_serial_id, (unsigned long long) ppp.last_purse_merge_serial_id, (unsigned long long) ppp.last_purse_deposits_serial_id, (unsigned long long) ppp.last_account_merge_serial_id); @@ -780,6 +1108,18 @@ analyze_purses (void *cls) } pc.purses = GNUNET_CONTAINER_multihashmap_create (512, GNUNET_NO); + + qs = TALER_ARL_edb->select_purse_requests_above_serial_id ( + TALER_ARL_edb->cls, + ppp.last_purse_request_serial_id, + &handle_purse_requested, + &pc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + qs = TALER_ARL_edb->select_purse_merges_above_serial_id ( TALER_ARL_edb->cls, ppp.last_purse_merge_serial_id, @@ -812,6 +1152,28 @@ analyze_purses (void *cls) return qs; } + qs = TALER_ARL_edb->select_all_purse_decisions_above_serial_id ( + TALER_ARL_edb->cls, + ppp.last_purse_decision_serial_id, + &handle_purse_decision, + &pc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + + qs = TALER_ARL_adb->select_purse_expired ( + TALER_ARL_adb->cls, + &TALER_ARL_master_pub, + &handle_purse_expired, + &pc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + GNUNET_CONTAINER_multihashmap_iterate (pc.purses, &verify_purse_balance, &pc); @@ -853,7 +1215,9 @@ analyze_purses (void *cls) return qs; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Concluded purse audit step at %llu/%llu/%llu\n", + "Concluded purse audit step at %llu/%llu/%llu/%llu/%llu\n", + (unsigned long long) ppp.last_purse_request_serial_id, + (unsigned long long) ppp.last_purse_decision_serial_id, (unsigned long long) ppp.last_purse_merge_serial_id, (unsigned long long) ppp.last_purse_deposits_serial_id, (unsigned long long) ppp.last_account_merge_serial_id); @@ -889,6 +1253,12 @@ run (void *cls, GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TALER_ARL_currency, &balance.balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TALER_ARL_currency, + &total_balance_insufficient_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (TALER_ARL_currency, + &total_delayed_decisions)); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TALER_ARL_currency, &total_arithmetic_delta_plus)); @@ -927,6 +1297,8 @@ run (void *cls, /* Globals (REVIEW!) */ TALER_JSON_pack_amount ("total_balance_insufficient", &total_balance_insufficient_loss), + TALER_JSON_pack_amount ("total_delayed_purse_decisions", + &total_delayed_decisions), GNUNET_JSON_pack_array_steal ( "purse_balance_insufficient_inconsistencies", report_purse_balance_insufficient_inconsistencies), diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c index 2bccd11f7..f3da2c85c 100644 --- a/src/exchange/taler-exchange-httpd_purses_create.c +++ b/src/exchange/taler-exchange-httpd_purses_create.c @@ -156,15 +156,15 @@ create_transaction (void *cls, uint32_t min_age; TEH_plugin->rollback (TEH_plugin->cls); - qs = TEH_plugin->select_purse_request (TEH_plugin->cls, - &pcc->pd.purse_pub, - &merge_pub, - &purse_expiration, - &h_contract_terms, - &min_age, - &target_amount, - &balance, - &purse_sig); + qs = TEH_plugin->get_purse_request (TEH_plugin->cls, + &pcc->pd.purse_pub, + &merge_pub, + &purse_expiration, + &h_contract_terms, + &min_age, + &target_amount, + &balance, + &purse_sig); if (qs < 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c index 581abe90a..dc1a256d7 100644 --- a/src/exchange/taler-exchange-httpd_purses_deposit.c +++ b/src/exchange/taler-exchange-httpd_purses_deposit.c @@ -359,11 +359,13 @@ TEH_handler_purses_deposit ( { enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp create_timestamp; struct GNUNET_TIME_Timestamp merge_timestamp; qs = TEH_plugin->select_purse ( TEH_plugin->cls, pcc.purse_pub, + &create_timestamp, &pcc.purse_expiration, &pcc.amount, &pcc.deposit_total, diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c index 3261ed34f..8384086b6 100644 --- a/src/exchange/taler-exchange-httpd_purses_get.c +++ b/src/exchange/taler-exchange-httpd_purses_get.c @@ -303,9 +303,11 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc, { enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Timestamp create_timestamp; qs = TEH_plugin->select_purse (TEH_plugin->cls, &gc->purse_pub, + &create_timestamp, &gc->purse_expiration, &gc->amount, &gc->deposited, diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c index c1582948b..0bb8a1867 100644 --- a/src/exchange/taler-exchange-httpd_purses_merge.c +++ b/src/exchange/taler-exchange-httpd_purses_merge.c @@ -426,15 +426,15 @@ TEH_handler_purses_merge ( } /* Fetch purse details */ - qs = TEH_plugin->select_purse_request (TEH_plugin->cls, - pcc.purse_pub, - &pcc.merge_pub, - &pcc.purse_expiration, - &pcc.h_contract_terms, - &pcc.min_age, - &pcc.target_amount, - &pcc.balance, - &purse_sig); + qs = TEH_plugin->get_purse_request (TEH_plugin->cls, + pcc.purse_pub, + &pcc.merge_pub, + &pcc.purse_expiration, + &pcc.h_contract_terms, + &pcc.min_age, + &pcc.target_amount, + &pcc.balance, + &purse_sig); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.c b/src/exchange/taler-exchange-httpd_reserves_purse.c index 79625a395..cc3ffeb15 100644 --- a/src/exchange/taler-exchange-httpd_reserves_purse.c +++ b/src/exchange/taler-exchange-httpd_reserves_purse.c @@ -252,7 +252,7 @@ purse_transaction (void *cls, uint32_t min_age; TEH_plugin->rollback (TEH_plugin->cls); - qs = TEH_plugin->select_purse_request ( + qs = TEH_plugin->get_purse_request ( TEH_plugin->cls, &rpc->pd.purse_pub, &merge_pub, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index c4957c912..5ccb71127 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -2259,6 +2259,7 @@ prepare_statements (struct PostgresClosure *pg) "select_purse", "SELECT " " merge_pub" + ",purse_creation" ",purse_expiration" ",h_contract_terms" ",amount_with_fee_val" @@ -2269,9 +2270,9 @@ prepare_statements (struct PostgresClosure *pg) " FROM purse_requests" " LEFT JOIN purse_merges USING (purse_pub)" " WHERE purse_pub=$1;"), - /* Used in #postgres_select_purse_request */ + /* Used in #postgres_get_purse_request */ GNUNET_PQ_make_prepare ( - "select_purse_request", + "get_purse_request", "SELECT " " merge_pub" ",purse_expiration" @@ -8001,6 +8002,127 @@ postgres_select_purse_decisions_above_serial_id ( } +/** + * Closure for #all_purse_decision_serial_helper_cb(). + */ +struct AllPurseDecisionSerialContext +{ + + /** + * Callback to call. + */ + TALER_EXCHANGEDB_AllPurseDecisionCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Plugin context. + */ + struct PostgresClosure *pg; + + /** + * Status code, set to #GNUNET_SYSERR on hard errors. + */ + enum GNUNET_GenericReturnValue status; +}; + + +/** + * Helper function to be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct PurseRefundSerialContext` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +all_purse_decision_serial_helper_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct AllPurseDecisionSerialContext *dsc = cls; + + for (unsigned int i = 0; istatus = GNUNET_SYSERR; + return; + } + ret = dsc->cb (dsc->cb_cls, + rowid, + &purse_pub, + refunded); + GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != ret) + break; + } +} + + +/** + * Select purse decisions above @a serial_id in monotonically increasing + * order. + * + * @param cls closure + * @param serial_id highest serial ID to exclude (select strictly larger) + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +postgres_select_all_purse_decisions_above_serial_id ( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_AllPurseDecisionCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&serial_id), + GNUNET_PQ_query_param_end + }; + struct AllPurseDecisionSerialContext dsc = { + .cb = cb, + .cb_cls = cb_cls, + .pg = pg, + .status = GNUNET_OK + }; + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "audit_get_all_purse_decision_incr", + params, + & + all_purse_decision_serial_helper_cb, + &dsc); + if (GNUNET_OK != dsc.status) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} + + /** * Closure for #purse_refund_coin_helper_cb(). */ @@ -11355,7 +11477,7 @@ postgres_insert_contract ( * @return transaction status code */ static enum GNUNET_DB_QueryStatus -postgres_select_purse_request ( +postgres_get_purse_request ( void *cls, const struct TALER_PurseContractPublicKeyP *purse_pub, struct TALER_PurseMergePublicKeyP *merge_pub, @@ -11389,7 +11511,7 @@ postgres_select_purse_request ( GNUNET_PQ_result_spec_end }; return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "select_purse_request", + "get_purse_request", params, rs); } @@ -11464,15 +11586,15 @@ postgres_insert_purse_request ( struct TALER_Amount balance; struct TALER_PurseContractSignatureP purse_sig2; - qs = postgres_select_purse_request (pg, - purse_pub, - &merge_pub2, - &purse_expiration2, - &h_contract_terms2, - &age_limit2, - &amount2, - &balance, - &purse_sig2); + qs = postgres_get_purse_request (pg, + purse_pub, + &merge_pub2, + &purse_expiration2, + &h_contract_terms2, + &age_limit2, + &amount2, + &balance, + &purse_sig2); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { GNUNET_break (0); @@ -11542,6 +11664,7 @@ postgres_expire_purse ( * * @param cls the @e cls of this struct with the plugin-specific state * @param purse_pub public key of the new purse + * @param[out] purse_creation set to time when the purse was created * @param[out] purse_expiration set to time when the purse will expire * @param[out] amount set to target amount (with fees) to be put into the purse * @param[out] deposited set to actual amount put into the purse so far @@ -11553,6 +11676,7 @@ static enum GNUNET_DB_QueryStatus postgres_select_purse ( void *cls, const struct TALER_PurseContractPublicKeyP *purse_pub, + struct GNUNET_TIME_Timestamp *purse_creation, struct GNUNET_TIME_Timestamp *purse_expiration, struct TALER_Amount *amount, struct TALER_Amount *deposited, @@ -11567,6 +11691,8 @@ postgres_select_purse ( struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_timestamp ("purse_expiration", purse_expiration), + GNUNET_PQ_result_spec_timestamp ("purse_creation", + purse_creation), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", amount), TALER_PQ_RESULT_SPEC_AMOUNT ("balance", @@ -13042,6 +13168,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_select_history_requests_above_serial_id; plugin->select_purse_decisions_above_serial_id = &postgres_select_purse_decisions_above_serial_id; + plugin->select_all_purse_decisions_above_serial_id + = &postgres_select_all_purse_decisions_above_serial_id; plugin->select_purse_deposits_by_purse = &postgres_select_purse_deposits_by_purse; plugin->select_refreshes_above_serial_id @@ -13136,8 +13264,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_select_contract_by_purse; plugin->insert_purse_request = &postgres_insert_purse_request; - plugin->select_purse_request - = &postgres_select_purse_request; + plugin->get_purse_request + = &postgres_get_purse_request; plugin->expire_purse = &postgres_expire_purse; plugin->select_purse diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h index 129b51714..709a956cb 100644 --- a/src/include/taler_auditordb_plugin.h +++ b/src/include/taler_auditordb_plugin.h @@ -604,6 +604,23 @@ typedef enum GNUNET_GenericReturnValue const struct TALER_AUDITORDB_DepositConfirmation *dc); +/** + * Function called on expired purses. + * + * @param cls closure + * @param purse_pub public key of the purse + * @param balance amount of money in the purse + * @param expiration_date when did the purse expire? + * @return #GNUNET_OK to continue to iterate + */ +typedef enum GNUNET_GenericReturnValue +(*TALER_AUDITORDB_ExpiredPurseCallback)( + void *cls, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_Amount *balance, + struct GNUNET_TIME_Timestamp expiration_date); + + /** * @brief The plugin API, returned from the plugin's "init" function. * The argument given to "init" is simply a configuration handle. @@ -1220,7 +1237,7 @@ struct TALER_AUDITORDB_Plugin * @param purse_pub public key of the purse * @param master_pub master public key of the exchange * @param balance balance of the purse - * @param expiration_date expiration date of the reserve + * @param expiration_date expiration date of the purse * @return transaction status code */ enum GNUNET_DB_QueryStatus @@ -1245,7 +1262,7 @@ struct TALER_AUDITORDB_Plugin enum GNUNET_DB_QueryStatus (*update_purse_info)( void *cls, - const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_PurseContractPublicKeyP *purse_pub, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_Amount *balance); @@ -1254,7 +1271,7 @@ struct TALER_AUDITORDB_Plugin * Get information about a purse. * * @param cls the @e cls of this struct with the plugin-specific state - * @param reserve_pub public key of the reserve + * @param purse_pub public key of the purse * @param master_pub master public key of the exchange * @param[out] rowid which row did we get the information from * @param[out] balance set to balance of the purse @@ -1271,6 +1288,38 @@ struct TALER_AUDITORDB_Plugin struct GNUNET_TIME_Timestamp *expiration_date); + /** + * Delete information about a purse. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param purse_pub public key of the reserve + * @param master_pub master public key of the exchange + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*delete_purse_info)( + void *cls, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_MasterPublicKeyP *master_pub); + + + /** + * Get information about expired purses. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param master_pub master public key of the exchange + * @param cb function to call on expired purses + * @param cb_cls closure for @a cb + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*select_purse_expired)( + void *cls, + const struct TALER_MasterPublicKeyP *master_pub, + TALER_AUDITORDB_ExpiredPurseCallback cb, + void *cb_cls); + + /** * Delete information about a purse. * diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index df992c6ee..2e85f6d91 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -2771,7 +2771,7 @@ TALER_CRYPTO_helper_esign_disconnect ( void TALER_wallet_purse_create_sign ( struct GNUNET_TIME_Timestamp purse_expiration, - struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_PurseMergePublicKeyP *merge_pub, uint32_t min_age, const struct TALER_Amount *amount, @@ -2794,7 +2794,7 @@ TALER_wallet_purse_create_sign ( enum GNUNET_GenericReturnValue TALER_wallet_purse_create_verify ( struct GNUNET_TIME_Timestamp purse_expiration, - struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_PurseMergePublicKeyP *merge_pub, uint32_t min_age, const struct TALER_Amount *amount, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 4965a27b1..de4babfd0 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2356,7 +2356,7 @@ typedef enum GNUNET_GenericReturnValue * @param cls closure * @param rowid unique serial ID for the deposit in our DB * @param purse_pub public key of the purse - * @param reserve_pub public key of the target reserve, NULL if not known + * @param reserve_pub public key of the target reserve, NULL if not known / refunded * @param purse_value what is the (target) value of the purse * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop */ @@ -2369,6 +2369,24 @@ typedef enum GNUNET_GenericReturnValue const struct TALER_Amount *purse_value); +/** + * Function called with details about purse decisions that have been made, with + * the goal of auditing the purse's execution. + * + * @param cls closure + * @param rowid unique serial ID for the deposit in our DB + * @param purse_pub public key of the purse + * @param refunded true if decision was to refund + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +typedef enum GNUNET_GenericReturnValue +(*TALER_EXCHANGEDB_AllPurseDecisionCallback)( + void *cls, + uint64_t rowid, + const struct TALER_PurseContractPublicKeyP *purse_pub, + bool refunded); + + /** * Function called with details about purse refunds that have been made, with * the goal of auditing the purse refund's execution. @@ -2993,6 +3011,34 @@ typedef void bool done); +/** + * Function called on purse requests. + * + * @param cls closure + * @param purse_pub public key of the purse + * @param merge_pub public key representing the merge capability + * @param purse_creation when was the purse created? + * @param purse_expiration when would an unmerged purse expire + * @param h_contract_terms contract associated with the purse + * @param age_limit the age limit for deposits into the purse + * @param target_amount amount to be put into the purse + * @param purse_sig signature of the purse over the initialization data + * @return #GNUNET_OK to continue to iterate + */ +typedef enum GNUNET_GenericReturnValue +(*TALER_EXCHANGEDB_PurseRequestCallback)( + void *cls, + uint64_t rowid, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + struct GNUNET_TIME_Timestamp purse_creation, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + uint32_t age_limit, + const struct TALER_Amount *target_amount, + const struct TALER_PurseContractSignatureP *purse_sig); + + /** * Function called with information about the exchange's denomination keys. * Note that the 'master' field in @a issue will not yet be initialized when @@ -4594,6 +4640,25 @@ struct TALER_EXCHANGEDB_Plugin TALER_EXCHANGEDB_DepositCallback cb, void *cb_cls); + + /** + * Function called to return meta data about a purses + * above a certain serial ID. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param serial_id number to select requests by + * @param cb function to call on each request + * @param cb_cls closure for @a cb + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*select_purse_requests_above_serial_id)( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_PurseRequestCallback cb, + void *cb_cls); + + /** * Select purse deposits above @a serial_id in monotonically increasing * order. @@ -4686,6 +4751,24 @@ struct TALER_EXCHANGEDB_Plugin void *cb_cls); + /** + * Select all purse refunds above @a serial_id in monotonically increasing + * order. + * + * @param cls closure + * @param serial_id highest serial ID to exclude (select strictly larger) + * @param cb function to call on each result + * @param cb_cls closure for @a cb + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*select_all_purse_decisions_above_serial_id)( + void *cls, + uint64_t serial_id, + TALER_EXCHANGEDB_AllPurseDecisionCallback cb, + void *cb_cls); + + /** * Select coins deposited into a purse. * @@ -5647,6 +5730,7 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls the @e cls of this struct with the plugin-specific state * @param purse_pub public key of the new purse + * @param[out] purse_creation set to time when the purse was created * @param[out] purse_expiration set to time when the purse will expire * @param[out] amount set to target amount (with fees) to be put into the purse * @param[out] deposited set to actual amount put into the purse so far @@ -5658,6 +5742,7 @@ struct TALER_EXCHANGEDB_Plugin (*select_purse)( void *cls, const struct TALER_PurseContractPublicKeyP *purse_pub, + struct GNUNET_TIME_Timestamp *purse_creation, struct GNUNET_TIME_Timestamp *purse_expiration, struct TALER_Amount *amount, struct TALER_Amount *deposited, @@ -5681,7 +5766,7 @@ struct TALER_EXCHANGEDB_Plugin * @return transaction status code */ enum GNUNET_DB_QueryStatus - (*select_purse_request)( + (*get_purse_request)( void *cls, const struct TALER_PurseContractPublicKeyP *purse_pub, struct TALER_PurseMergePublicKeyP *merge_pub, diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 192914394..6c8124d03 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -821,7 +821,7 @@ GNUNET_NETWORK_STRUCT_END void TALER_wallet_purse_create_sign ( struct GNUNET_TIME_Timestamp purse_expiration, - struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_PurseMergePublicKeyP *merge_pub, uint32_t min_age, const struct TALER_Amount *amount, @@ -848,7 +848,7 @@ TALER_wallet_purse_create_sign ( enum GNUNET_GenericReturnValue TALER_wallet_purse_create_verify ( struct GNUNET_TIME_Timestamp purse_expiration, - struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PrivateContractHashP *h_contract_terms, const struct TALER_PurseMergePublicKeyP *merge_pub, uint32_t min_age, const struct TALER_Amount *amount,