diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-03-20 22:34:17 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-03-20 22:34:17 +0100 |
commit | 66616a97d77d37ab0a1358f3678a07223e624636 (patch) | |
tree | d3c85ae37e689696770c76cce759f746ce77c851 /src/auditor/taler-auditor-reserves.c | |
parent | 1b24e2f9bb84d64ff07f28a330e1913de790df0c (diff) | |
download | exchange-66616a97d77d37ab0a1358f3678a07223e624636.tar.xz |
working on splitting auditor
Diffstat (limited to 'src/auditor/taler-auditor-reserves.c')
-rw-r--r-- | src/auditor/taler-auditor-reserves.c | 1641 |
1 files changed, 1641 insertions, 0 deletions
diff --git a/src/auditor/taler-auditor-reserves.c b/src/auditor/taler-auditor-reserves.c new file mode 100644 index 000000000..2fe103c81 --- /dev/null +++ b/src/auditor/taler-auditor-reserves.c @@ -0,0 +1,1641 @@ +/* + This file is part of TALER + Copyright (C) 2016-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero Public License for more details. + + You should have received a copy of the GNU Affero Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file auditor/taler-auditor-reserves.c + * @brief audits the reserves of an exchange database + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_auditordb_plugin.h" +#include "taler_exchangedb_lib.h" +#include "taler_json_lib.h" +#include "taler_bank_service.h" +#include "taler_signatures.h" +#include "report-lib.h" + + +/** + * Use a 1 day grace period to deal with clocks not being perfectly synchronized. + */ +#define CLOSING_GRACE_PERIOD GNUNET_TIME_UNIT_DAYS + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * After how long should idle reserves be closed? + */ +static struct GNUNET_TIME_Relative idle_reserve_expiration_time; + +/** + * Checkpointing our progress for reserves. + */ +static struct TALER_AUDITORDB_ProgressPointReserve ppr; + +/** + * Checkpointing our progress for reserves. + */ +static struct TALER_AUDITORDB_ProgressPointReserve ppr_start; + +/** + * Array of reports about row inconsitencies. + */ +static json_t *report_row_inconsistencies; + +/** + * Array of reports about the denomination key not being + * valid at the time of withdrawal. + */ +static json_t *denomination_key_validity_withdraw_inconsistencies; + +/** + * Array of reports about reserve balance insufficient inconsitencies. + */ +static json_t *report_reserve_balance_insufficient_inconsistencies; + +/** + * Total amount reserves were charged beyond their balance. + */ +static struct TALER_Amount total_balance_insufficient_loss; + +/** + * Array of reports about reserve balance summary wrong in database. + */ +static json_t *report_reserve_balance_summary_wrong_inconsistencies; + +/** + * Total delta between expected and stored reserve balance summaries, + * for positive deltas. + */ +static struct TALER_Amount total_balance_summary_delta_plus; + +/** + * Total delta between expected and stored reserve balance summaries, + * for negative deltas. + */ +static struct TALER_Amount total_balance_summary_delta_minus; + +/** + * Array of reports about reserve's not being closed inconsitencies. + */ +static json_t *report_reserve_not_closed_inconsistencies; + +/** + * Total amount affected by reserves not having been closed on time. + */ +static struct TALER_Amount total_balance_reserve_not_closed; + +/** + * Report about amount calculation differences (causing profit + * or loss at the exchange). + */ +static json_t *report_amount_arithmetic_inconsistencies; + +/** + * Profits the exchange made by bad amount calculations. + */ +static struct TALER_Amount total_arithmetic_delta_plus; + +/** + * Losses the exchange made by bad amount calculations. + */ +static struct TALER_Amount total_arithmetic_delta_minus; + +/** + * Expected balance in the escrow account. + */ +static struct TALER_Amount total_escrow_balance; + +/** + * Recoups we made on denominations that were not revoked (!?). + */ +static struct TALER_Amount total_irregular_recoups; + +/** + * Total withdraw fees earned. + */ +static struct TALER_Amount total_withdraw_fee_income; + +/** + * Array of reports about coin operations with bad signatures. + */ +static json_t *report_bad_sig_losses; + +/** + * Total amount lost by operations for which signatures were invalid. + */ +static struct TALER_Amount total_bad_sig_loss; + + +/* ***************************** Report logic **************************** */ + + +/** + * Report a (serious) inconsistency in the exchange's database with + * respect to calculations involving amounts. + * + * @param operation what operation had the inconsistency + * @param rowid affected row, UINT64_MAX if row is missing + * @param exchange amount calculated by exchange + * @param auditor amount calculated by auditor + * @param profitable 1 if @a exchange being larger than @a auditor is + * profitable for the exchange for this operation, + * -1 if @a exchange being smaller than @a auditor is + * profitable for the exchange, and 0 if it is unclear + */ +static void +report_amount_arithmetic_inconsistency (const char *operation, + uint64_t rowid, + const struct TALER_Amount *exchange, + const struct TALER_Amount *auditor, + int profitable) +{ + struct TALER_Amount delta; + struct TALER_Amount *target; + + if (0 < TALER_amount_cmp (exchange, + auditor)) + { + /* exchange > auditor */ + GNUNET_break (GNUNET_OK == + TALER_amount_subtract (&delta, + exchange, + auditor)); + } + else + { + /* auditor < exchange */ + profitable = -profitable; + GNUNET_break (GNUNET_OK == + TALER_amount_subtract (&delta, + auditor, + exchange)); + } + report (report_amount_arithmetic_inconsistencies, + json_pack ("{s:s, s:I, s:o, s:o, s:I}", + "operation", operation, + "rowid", (json_int_t) rowid, + "exchange", TALER_JSON_from_amount (exchange), + "auditor", TALER_JSON_from_amount (auditor), + "profitable", (json_int_t) profitable)); + if (0 != profitable) + { + target = (1 == profitable) + ? &total_arithmetic_delta_plus + : &total_arithmetic_delta_minus; + GNUNET_break (GNUNET_OK == + TALER_amount_add (target, + target, + &delta)); + } +} + + +/** + * Report a (serious) inconsistency in the exchange's database. + * + * @param table affected table + * @param rowid affected row, UINT64_MAX if row is missing + * @param diagnostic message explaining the problem + */ +static void +report_row_inconsistency (const char *table, + uint64_t rowid, + const char *diagnostic) +{ + report (report_row_inconsistencies, + json_pack ("{s:s, s:I, s:s}", + "table", table, + "row", (json_int_t) rowid, + "diagnostic", diagnostic)); +} + + +/* ***************************** Analyze reserves ************************ */ +/* This logic checks the reserves_in, reserves_out and reserves-tables */ + +/** + * Summary data we keep per reserve. + */ +struct ReserveSummary +{ + /** + * Public key of the reserve. + * Always set when the struct is first initialized. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Sum of all incoming transfers during this transaction. + * Updated only in #handle_reserve_in(). + */ + struct TALER_Amount total_in; + + /** + * Sum of all outgoing transfers during this transaction (includes fees). + * Updated only in #handle_reserve_out(). + */ + struct TALER_Amount total_out; + + /** + * Sum of withdraw fees encountered during this transaction. + */ + struct TALER_Amount total_fee; + + /** + * Previous balance of the reserve as remembered by the auditor. + * (updated based on @e total_in and @e total_out at the end). + */ + struct TALER_Amount a_balance; + + /** + * Previous withdraw fee balance of the reserve, as remembered by the auditor. + * (updated based on @e total_fee at the end). + */ + struct TALER_Amount a_withdraw_fee_balance; + + /** + * Previous reserve expiration data, as remembered by the auditor. + * (updated on-the-fly in #handle_reserve_in()). + */ + struct GNUNET_TIME_Absolute a_expiration_date; + + /** + * Which account did originally put money into the reserve? + */ + char *sender_account; + + /** + * Did we have a previous reserve info? Used to decide between + * UPDATE and INSERT later. Initialized in + * #load_auditor_reserve_summary() together with the a-* values + * (if available). + */ + int had_ri; + +}; + + +/** + * Load the auditor's remembered state about the reserve into @a rs. + * The "total_in" and "total_out" amounts of @a rs must already be + * initialized (so we can determine the currency). + * + * @param[in,out] rs reserve summary to (fully) initialize + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +load_auditor_reserve_summary (struct ReserveSummary *rs) +{ + enum GNUNET_DB_QueryStatus qs; + uint64_t rowid; + + qs = adb->get_reserve_info (adb->cls, + asession, + &rs->reserve_pub, + &master_pub, + &rowid, + &rs->a_balance, + &rs->a_withdraw_fee_balance, + &rs->a_expiration_date, + &rs->sender_account); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + rs->had_ri = GNUNET_NO; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (rs->total_in.currency, + &rs->a_balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (rs->total_in.currency, + &rs->a_withdraw_fee_balance)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating fresh reserve `%s' with starting balance %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&rs->a_balance)); + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + rs->had_ri = GNUNET_YES; + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&rs->a_balance, + &rs->a_withdraw_fee_balance)) || + (GNUNET_YES != + TALER_amount_cmp_currency (&rs->total_in, + &rs->a_balance)) ) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Auditor remembers reserve `%s' has balance %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&rs->a_balance)); + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Closure to the various callbacks we make while checking a reserve. + */ +struct ReserveContext +{ + /** + * Map from hash of reserve's public key to a `struct ReserveSummary`. + */ + struct GNUNET_CONTAINER_MultiHashMap *reserves; + + /** + * Map from hash of denomination's public key to a + * static string "revoked" for keys that have been revoked, + * or "master signature invalid" in case the revocation is + * there but bogus. + */ + struct GNUNET_CONTAINER_MultiHashMap *revoked; + + /** + * Transaction status code, set to error codes if applicable. + */ + enum GNUNET_DB_QueryStatus qs; + +}; + + +/** + * Function called with details about incoming wire transfers. + * + * @param cls our `struct ReserveContext` + * @param rowid unique serial ID for the refresh session in our DB + * @param reserve_pub public key of the reserve (also the WTID) + * @param credit amount that was received + * @param sender_account_details information about the sender's bank account + * @param wire_reference unique reference identifying the wire transfer + * @param execution_date when did we receive the funds + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +handle_reserve_in (void *cls, + uint64_t rowid, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *credit, + const char *sender_account_details, + uint64_t wire_reference, + struct GNUNET_TIME_Absolute execution_date) +{ + struct ReserveContext *rc = cls; + struct GNUNET_HashCode key; + struct ReserveSummary *rs; + struct GNUNET_TIME_Absolute expiry; + enum GNUNET_DB_QueryStatus qs; + + (void) wire_reference; + /* should be monotonically increasing */ + GNUNET_assert (rowid >= ppr.last_reserve_in_serial_id); + ppr.last_reserve_in_serial_id = rowid + 1; + + GNUNET_CRYPTO_hash (reserve_pub, + sizeof (*reserve_pub), + &key); + rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, + &key); + if (NULL == rs) + { + rs = GNUNET_new (struct ReserveSummary); + rs->sender_account = GNUNET_strdup (sender_account_details); + rs->reserve_pub = *reserve_pub; + rs->total_in = *credit; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (credit->currency, + &rs->total_out)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (credit->currency, + &rs->total_fee)); + if (0 > (qs = load_auditor_reserve_summary (rs))) + { + GNUNET_break (0); + GNUNET_free (rs); + rc->qs = qs; + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (rc->reserves, + &key, + rs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + else + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_in, + &rs->total_in, + credit)); + if (NULL == rs->sender_account) + rs->sender_account = GNUNET_strdup (sender_account_details); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Additional incoming wire transfer for reserve `%s' of %s\n", + TALER_B2S (reserve_pub), + TALER_amount2s (credit)); + expiry = GNUNET_TIME_absolute_add (execution_date, + idle_reserve_expiration_time); + rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, + expiry); + return GNUNET_OK; +} + + +/** + * Function called with details about withdraw operations. Verifies + * the signature and updates the reserve's balance. + * + * @param cls our `struct ReserveContext` + * @param rowid unique serial ID for the refresh session in our DB + * @param h_blind_ev blinded hash of the coin's public key + * @param denom_pub public denomination key of the deposited coin + * @param reserve_pub public key of the reserve + * @param reserve_sig signature over the withdraw operation + * @param execution_date when did the wallet withdraw the coin + * @param amount_with_fee amount that was withdrawn + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +handle_reserve_out (void *cls, + uint64_t rowid, + const struct GNUNET_HashCode *h_blind_ev, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig, + struct GNUNET_TIME_Absolute execution_date, + const struct TALER_Amount *amount_with_fee) +{ + struct ReserveContext *rc = cls; + struct TALER_WithdrawRequestPS wsrd; + struct GNUNET_HashCode key; + struct ReserveSummary *rs; + const struct TALER_DenominationKeyValidityPS *issue; + struct TALER_Amount withdraw_fee; + struct GNUNET_TIME_Absolute valid_start; + struct GNUNET_TIME_Absolute expire_withdraw; + enum GNUNET_DB_QueryStatus qs; + + /* should be monotonically increasing */ + GNUNET_assert (rowid >= ppr.last_reserve_out_serial_id); + ppr.last_reserve_out_serial_id = rowid + 1; + + /* lookup denomination pub data (make sure denom_pub is valid, establish fees) */ + qs = get_denomination_info (denom_pub, + &issue, + &wsrd.h_denomination_pub); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Hard database error trying to get denomination %s (%s) from database!\n", + TALER_B2S (denom_pub), + TALER_amount2s (amount_with_fee)); + rc->qs = qs; + return GNUNET_SYSERR; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("withdraw", + rowid, + "denomination key not found"); + return GNUNET_OK; + } + + /* check that execution date is within withdraw range for denom_pub */ + valid_start = GNUNET_TIME_absolute_ntoh (issue->start); + expire_withdraw = GNUNET_TIME_absolute_ntoh (issue->expire_withdraw); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Checking withdraw timing: %llu, expire: %llu, timing: %llu\n", + (unsigned long long) valid_start.abs_value_us, + (unsigned long long) expire_withdraw.abs_value_us, + (unsigned long long) execution_date.abs_value_us); + if ( (valid_start.abs_value_us > execution_date.abs_value_us) || + (expire_withdraw.abs_value_us < execution_date.abs_value_us) ) + { + report (denomination_key_validity_withdraw_inconsistencies, + json_pack ("{s:I, s:o, s:o, s:o}", + "row", (json_int_t) rowid, + "execution_date", + json_from_time_abs (execution_date), + "reserve_pub", GNUNET_JSON_from_data_auto (reserve_pub), + "denompub_h", GNUNET_JSON_from_data_auto ( + &wsrd.h_denomination_pub))); + } + + /* check reserve_sig */ + wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW); + wsrd.purpose.size = htonl (sizeof (wsrd)); + wsrd.reserve_pub = *reserve_pub; + TALER_amount_hton (&wsrd.amount_with_fee, + amount_with_fee); + wsrd.withdraw_fee = issue->fee_withdraw; + wsrd.h_coin_envelope = *h_blind_ev; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &wsrd.purpose, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub)) + { + report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "withdraw", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount (amount_with_fee), + "key_pub", GNUNET_JSON_from_data_auto (reserve_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount_with_fee)); + return GNUNET_OK; + } + + GNUNET_CRYPTO_hash (reserve_pub, + sizeof (*reserve_pub), + &key); + rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, + &key); + if (NULL == rs) + { + rs = GNUNET_new (struct ReserveSummary); + rs->reserve_pub = *reserve_pub; + rs->total_out = *amount_with_fee; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount_with_fee->currency, + &rs->total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount_with_fee->currency, + &rs->total_fee)); + qs = load_auditor_reserve_summary (rs); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_free (rs); + rc->qs = qs; + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (rc->reserves, + &key, + rs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + else + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_out, + &rs->total_out, + amount_with_fee)); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Reserve `%s' reduced by %s from withdraw\n", + TALER_B2S (reserve_pub), + TALER_amount2s (amount_with_fee)); + TALER_amount_ntoh (&withdraw_fee, + &issue->fee_withdraw); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Increasing withdraw profits by fee %s\n", + TALER_amount2s (&withdraw_fee)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_fee, + &rs->total_fee, + &withdraw_fee)); + + return GNUNET_OK; +} + + +/** + * Function called with details about withdraw operations. Verifies + * the signature and updates the reserve's balance. + * + * @param cls our `struct ReserveContext` + * @param rowid unique serial ID for the refresh session in our DB + * @param timestamp when did we receive the recoup request + * @param amount how much should be added back to the reserve + * @param reserve_pub public key of the reserve + * @param coin public information about the coin, denomination signature is + * already verified in #check_recoup() + * @param denom_pub public key of the denomionation of @a coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +handle_recoup_by_reserve (void *cls, + uint64_t rowid, + struct GNUNET_TIME_Absolute timestamp, + const struct TALER_Amount *amount, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_CoinPublicInfo *coin, + const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct + TALER_DenominationBlindingKeyP *coin_blind) +{ + struct ReserveContext *rc = cls; + struct GNUNET_HashCode key; + struct ReserveSummary *rs; + struct GNUNET_TIME_Absolute expiry; + struct TALER_RecoupRequestPS pr; + struct TALER_MasterSignatureP msig; + uint64_t rev_rowid; + enum GNUNET_DB_QueryStatus qs; + const char *rev; + + (void) denom_pub; + /* should be monotonically increasing */ + GNUNET_assert (rowid >= ppr.last_reserve_recoup_serial_id); + ppr.last_reserve_recoup_serial_id = rowid + 1; + /* We know that denom_pub matches denom_pub_hash because this + is how the SQL statement joined the tables. */ + pr.h_denom_pub = coin->denom_pub_hash; + pr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP); + pr.purpose.size = htonl (sizeof (pr)); + pr.coin_pub = coin->coin_pub; + pr.coin_blind = *coin_blind; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, + &pr.purpose, + &coin_sig->eddsa_signature, + &coin->coin_pub.eddsa_pub)) + { + report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "recoup", + "row", (json_int_t) rowid, + "loss", TALER_JSON_from_amount (amount), + "key_pub", GNUNET_JSON_from_data_auto ( + &coin->coin_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount)); + } + + /* check that the coin was eligible for recoup!*/ + rev = GNUNET_CONTAINER_multihashmap_get (rc->revoked, + &pr.h_denom_pub); + if (NULL == rev) + { + qs = edb->get_denomination_revocation (edb->cls, + esession, + &pr.h_denom_pub, + &msig, + &rev_rowid); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + rc->qs = qs; + return GNUNET_SYSERR; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + report_row_inconsistency ("recoup", + rowid, + "denomination key not in revocation set"); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_irregular_recoups, + &total_irregular_recoups, + amount)); + } + else + { + /* verify msig */ + struct TALER_MasterDenominationKeyRevocationPS kr; + + kr.purpose.purpose = htonl ( + TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED); + kr.purpose.size = htonl (sizeof (kr)); + kr.h_denom_pub = pr.h_denom_pub; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED, + &kr.purpose, + &msig.eddsa_signature, + &master_pub.eddsa_pub)) + { + rev = "master signature invalid"; + } + else + { + rev = "revoked"; + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (rc->revoked, + &pr.h_denom_pub, + (void *) rev, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + } + else + { + rev_rowid = 0; /* reported elsewhere */ + } + if ( (NULL != rev) && + (0 == strcmp (rev, "master signature invalid")) ) + { + report (report_bad_sig_losses, + json_pack ("{s:s, s:I, s:o, s:o}", + "operation", "recoup-master", + "row", (json_int_t) rev_rowid, + "loss", TALER_JSON_from_amount (amount), + "key_pub", GNUNET_JSON_from_data_auto (&master_pub))); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_bad_sig_loss, + &total_bad_sig_loss, + amount)); + } + + GNUNET_CRYPTO_hash (reserve_pub, + sizeof (*reserve_pub), + &key); + rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, + &key); + if (NULL == rs) + { + rs = GNUNET_new (struct ReserveSummary); + rs->reserve_pub = *reserve_pub; + rs->total_in = *amount; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount->currency, + &rs->total_out)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount->currency, + &rs->total_fee)); + qs = load_auditor_reserve_summary (rs); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_free (rs); + rc->qs = qs; + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (rc->reserves, + &key, + rs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + else + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_in, + &rs->total_in, + amount)); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Additional /recoup value to for reserve `%s' of %s\n", + TALER_B2S (reserve_pub), + TALER_amount2s (amount)); + expiry = GNUNET_TIME_absolute_add (timestamp, + idle_reserve_expiration_time); + rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, + expiry); + return GNUNET_OK; +} + + +/** + * Obtain the closing fee for a transfer at @a time for target + * @a receiver_account. + * + * @param receiver_account payto:// URI of the target account + * @param atime when was the transfer made + * @param[out] fee set to the closing fee + * @return #GNUNET_OK on success + */ +static int +get_closing_fee (const char *receiver_account, + struct GNUNET_TIME_Absolute atime, + struct TALER_Amount *fee) +{ + struct TALER_MasterSignatureP master_sig; + struct GNUNET_TIME_Absolute start_date; + struct GNUNET_TIME_Absolute end_date; + struct TALER_Amount wire_fee; + char *method; + + method = TALER_payto_get_method (receiver_account); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Method is `%s'\n", + method); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + edb->get_wire_fee (edb->cls, + esession, + method, + atime, + &start_date, + &end_date, + &wire_fee, + fee, + &master_sig)) + { + report_row_inconsistency ("closing-fee", + atime.abs_value_us, + "closing fee unavailable at given time"); + GNUNET_free (method); + return GNUNET_SYSERR; + } + GNUNET_free (method); + return GNUNET_OK; +} + + +/** + * Function called about reserve closing operations + * the aggregator triggered. + * + * @param cls closure + * @param rowid row identifier used to uniquely identify the reserve closing operation + * @param execution_date when did we execute the close operation + * @param amount_with_fee how much did we debit the reserve + * @param closing_fee how much did we charge for closing the reserve + * @param reserve_pub public key of the reserve + * @param receiver_account where did we send the funds + * @param transfer_details details about the wire transfer + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +handle_reserve_closed (void *cls, + uint64_t rowid, + struct GNUNET_TIME_Absolute execution_date, + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *closing_fee, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *receiver_account, + const struct + TALER_WireTransferIdentifierRawP *transfer_details) +{ + struct ReserveContext *rc = cls; + struct GNUNET_HashCode key; + struct ReserveSummary *rs; + enum GNUNET_DB_QueryStatus qs; + + (void) transfer_details; + /* should be monotonically increasing */ + GNUNET_assert (rowid >= ppr.last_reserve_close_serial_id); + ppr.last_reserve_close_serial_id = rowid + 1; + + GNUNET_CRYPTO_hash (reserve_pub, + sizeof (*reserve_pub), + &key); + rs = GNUNET_CONTAINER_multihashmap_get (rc->reserves, + &key); + if (NULL == rs) + { + rs = GNUNET_new (struct ReserveSummary); + rs->reserve_pub = *reserve_pub; + rs->total_out = *amount_with_fee; + rs->total_fee = *closing_fee; + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (amount_with_fee->currency, + &rs->total_in)); + qs = load_auditor_reserve_summary (rs); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + GNUNET_free (rs); + rc->qs = qs; + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (rc->reserves, + &key, + rs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + else + { + struct TALER_Amount expected_fee; + + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_out, + &rs->total_out, + amount_with_fee)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&rs->total_fee, + &rs->total_fee, + closing_fee)); + /* verify closing_fee is correct! */ + if (GNUNET_OK != + get_closing_fee (receiver_account, + execution_date, + &expected_fee)) + { + GNUNET_break (0); + } + else if (0 != TALER_amount_cmp (&expected_fee, + closing_fee)) + { + report_amount_arithmetic_inconsistency ("closing aggregation fee", + rowid, + closing_fee, + &expected_fee, + 1); + } + } + if (NULL == rs->sender_account) + { + GNUNET_break (GNUNET_NO == rs->had_ri); + report_row_inconsistency ("reserves_close", + rowid, + "target account not verified, auditor does not know reserve"); + } + else if (0 != strcmp (rs->sender_account, + receiver_account)) + { + report_row_inconsistency ("reserves_close", + rowid, + "target account does not match origin account"); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Additional closing operation for reserve `%s' of %s\n", + TALER_B2S (reserve_pub), + TALER_amount2s (amount_with_fee)); + return GNUNET_OK; +} + + +/** + * Check that the reserve summary matches what the exchange database + * thinks about the reserve, and update our own state of the reserve. + * + * Remove all reserves that we are happy with from the DB. + * + * @param cls our `struct ReserveContext` + * @param key hash of the reserve public key + * @param value a `struct ReserveSummary` + * @return #GNUNET_OK to process more entries + */ +static int +verify_reserve_balance (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct ReserveContext *rc = cls; + struct ReserveSummary *rs = value; + struct TALER_EXCHANGEDB_Reserve reserve; + struct TALER_Amount balance; + struct TALER_Amount nbalance; + struct TALER_Amount cfee; + enum GNUNET_DB_QueryStatus qs; + int ret; + + ret = GNUNET_OK; + reserve.pub = rs->reserve_pub; + qs = edb->reserves_get (edb->cls, + esession, + &reserve); + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + { + char *diag; + + GNUNET_asprintf (&diag, + "Failed to find summary for reserve `%s'\n", + TALER_B2S (&rs->reserve_pub)); + report_row_inconsistency ("reserve-summary", + UINT64_MAX, + diag); + GNUNET_free (diag); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_break (0); + qs = GNUNET_DB_STATUS_HARD_ERROR; + } + rc->qs = qs; + return GNUNET_OK; + } + + if (GNUNET_OK != + TALER_amount_add (&balance, + &rs->total_in, + &rs->a_balance)) + { + GNUNET_break (0); + goto cleanup; + } + + if (GNUNET_SYSERR == + TALER_amount_subtract (&nbalance, + &balance, + &rs->total_out)) + { + struct TALER_Amount loss; + + GNUNET_break (GNUNET_SYSERR != + TALER_amount_subtract (&loss, + &rs->total_out, + &balance)); + GNUNET_break (GNUNET_OK == + TALER_amount_add (&total_balance_insufficient_loss, + &total_balance_insufficient_loss, + &loss)); + report (report_reserve_balance_insufficient_inconsistencies, + json_pack ("{s:o, s:o}", + "reserve_pub", + GNUNET_JSON_from_data_auto (&rs->reserve_pub), + "loss", + TALER_JSON_from_amount (&loss))); + goto cleanup; + } + if (0 != TALER_amount_cmp (&nbalance, + &reserve.balance)) + { + struct TALER_Amount delta; + + if (0 < TALER_amount_cmp (&nbalance, + &reserve.balance)) + { + /* balance > reserve.balance */ + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&delta, + &nbalance, + &reserve.balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&total_balance_summary_delta_plus, + &total_balance_summary_delta_plus, + &delta)); + } + else + { + /* balance < reserve.balance */ + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&delta, + &reserve.balance, + &nbalance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&total_balance_summary_delta_minus, + &total_balance_summary_delta_minus, + &delta)); + } + report (report_reserve_balance_summary_wrong_inconsistencies, + json_pack ("{s:o, s:o, s:o}", + "reserve_pub", + GNUNET_JSON_from_data_auto (&rs->reserve_pub), + "exchange", + TALER_JSON_from_amount (&reserve.balance), + "auditor", + TALER_JSON_from_amount (&nbalance))); + goto cleanup; + } + + /* Check that reserve is being closed if it is past its expiration date */ + + if (CLOSING_GRACE_PERIOD.rel_value_us < + GNUNET_TIME_absolute_get_duration (rs->a_expiration_date).rel_value_us) + { + if ( (NULL != rs->sender_account) && + (GNUNET_OK == + get_closing_fee (rs->sender_account, + rs->a_expiration_date, + &cfee)) ) + { + if (1 == TALER_amount_cmp (&nbalance, + &cfee)) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&total_balance_reserve_not_closed, + &total_balance_reserve_not_closed, + &nbalance)); + report (report_reserve_not_closed_inconsistencies, + json_pack ("{s:o, s:o, s:o}", + "reserve_pub", + GNUNET_JSON_from_data_auto (&rs->reserve_pub), + "balance", + TALER_JSON_from_amount (&nbalance), + "expiration_time", + json_from_time_abs (rs->a_expiration_date))); + } + } + else + { + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&total_balance_reserve_not_closed, + &total_balance_reserve_not_closed, + &nbalance)); + report (report_reserve_not_closed_inconsistencies, + json_pack ("{s:o, s:o, s:o, s:s}", + "reserve_pub", + GNUNET_JSON_from_data_auto (&rs->reserve_pub), + "balance", + TALER_JSON_from_amount (&nbalance), + "expiration_time", + json_from_time_abs (rs->a_expiration_date), + "diagnostic", + "could not determine closing fee")); + } + } + + /* Add withdraw fees we encountered to totals */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Reserve reserve `%s' made %s in withdraw fees\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&rs->total_fee)); + if (GNUNET_YES != + TALER_amount_add (&rs->a_withdraw_fee_balance, + &rs->a_withdraw_fee_balance, + &rs->total_fee)) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + goto cleanup; + } + if ( (GNUNET_YES != + TALER_amount_add (&total_escrow_balance, + &total_escrow_balance, + &rs->total_in)) || + (GNUNET_SYSERR == + TALER_amount_subtract (&total_escrow_balance, + &total_escrow_balance, + &rs->total_out)) || + (GNUNET_YES != + TALER_amount_add (&total_withdraw_fee_income, + &total_withdraw_fee_income, + &rs->total_fee)) ) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + goto cleanup; + } + + if ( (0ULL == balance.value) && + (0U == balance.fraction) ) + { + /* balance is zero, drop reserve details (and then do not update/insert) */ + if (rs->had_ri) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance of reserve `%s' is %s, dropping it\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&nbalance)); + qs = adb->del_reserve_info (adb->cls, + asession, + &rs->reserve_pub, + &master_pub); + if (0 >= qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + ret = GNUNET_SYSERR; + rc->qs = qs; + goto cleanup; + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Final balance of reserve `%s' is %s, no need to remember it\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&nbalance)); + } + ret = GNUNET_OK; + goto cleanup; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Remembering final balance of reserve `%s' as %s\n", + TALER_B2S (&rs->reserve_pub), + TALER_amount2s (&nbalance)); + + if (rs->had_ri) + qs = adb->update_reserve_info (adb->cls, + asession, + &rs->reserve_pub, + &master_pub, + &nbalance, + &rs->a_withdraw_fee_balance, + rs->a_expiration_date); + else + qs = adb->insert_reserve_info (adb->cls, + asession, + &rs->reserve_pub, + &master_pub, + &nbalance, + &rs->a_withdraw_fee_balance, + rs->a_expiration_date, + rs->sender_account); + if (0 >= qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + ret = GNUNET_SYSERR; + rc->qs = qs; + } +cleanup: + GNUNET_assert (GNUNET_YES == + GNUNET_CONTAINER_multihashmap_remove (rc->reserves, + key, + rs)); + GNUNET_free_non_null (rs->sender_account); + GNUNET_free (rs); + return ret; +} + + +/** + * Analyze reserves for being well-formed. + * + * @param cls NULL + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +analyze_reserves (void *cls) +{ + struct ReserveContext rc; + enum GNUNET_DB_QueryStatus qsx; + enum GNUNET_DB_QueryStatus qs; + enum GNUNET_DB_QueryStatus qsp; + + (void) cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Analyzing reserves\n"); + qsp = adb->get_auditor_progress_reserve (adb->cls, + asession, + &master_pub, + &ppr); + if (0 > qsp) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp); + return qsp; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsp) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + _ ( + "First analysis using this auditor, starting audit from scratch\n")); + } + else + { + ppr_start = ppr; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ ("Resuming reserve audit at %llu/%llu/%llu/%llu\n"), + (unsigned long long) ppr.last_reserve_in_serial_id, + (unsigned long long) ppr.last_reserve_out_serial_id, + (unsigned long long) ppr.last_reserve_recoup_serial_id, + (unsigned long long) ppr.last_reserve_close_serial_id); + } + rc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + qsx = adb->get_reserve_summary (adb->cls, + asession, + &master_pub, + &total_escrow_balance, + &total_withdraw_fee_income); + if (qsx < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsx); + return qsx; + } + rc.reserves = GNUNET_CONTAINER_multihashmap_create (512, + GNUNET_NO); + rc.revoked = GNUNET_CONTAINER_multihashmap_create (4, + GNUNET_NO); + + qs = edb->select_reserves_in_above_serial_id (edb->cls, + esession, + ppr.last_reserve_in_serial_id, + &handle_reserve_in, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + qs = edb->select_withdrawals_above_serial_id (edb->cls, + esession, + ppr.last_reserve_out_serial_id, + &handle_reserve_out, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + qs = edb->select_recoup_above_serial_id (edb->cls, + esession, + ppr.last_reserve_recoup_serial_id, + &handle_recoup_by_reserve, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + qs = edb->select_reserve_closed_above_serial_id (edb->cls, + esession, + ppr. + last_reserve_close_serial_id, + &handle_reserve_closed, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + + GNUNET_CONTAINER_multihashmap_iterate (rc.reserves, + &verify_reserve_balance, + &rc); + GNUNET_break (0 == + GNUNET_CONTAINER_multihashmap_size (rc.reserves)); + GNUNET_CONTAINER_multihashmap_destroy (rc.reserves); + GNUNET_CONTAINER_multihashmap_destroy (rc.revoked); + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != rc.qs) + return qs; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qsx) + { + qs = adb->insert_reserve_summary (adb->cls, + asession, + &master_pub, + &total_escrow_balance, + &total_withdraw_fee_income); + } + else + { + qs = adb->update_reserve_summary (adb->cls, + asession, + &master_pub, + &total_escrow_balance, + &total_withdraw_fee_income); + } + if (0 >= qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qsp) + qs = adb->update_auditor_progress_reserve (adb->cls, + asession, + &master_pub, + &ppr); + else + qs = adb->insert_auditor_progress_reserve (adb->cls, + asession, + &master_pub, + &ppr); + if (0 >= qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to update auditor DB, not recording progress\n"); + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ ("Concluded reserve audit step at %llu/%llu/%llu/%llu\n"), + (unsigned long long) ppr.last_reserve_in_serial_id, + (unsigned long long) ppr.last_reserve_out_serial_id, + (unsigned long long) ppr.last_reserve_recoup_serial_id, + (unsigned long long) ppr.last_reserve_close_serial_id); + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + json_t *report; + + (void) cls; + (void) args; + (void) cfgfile; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Launching auditor\n"); + if (GNUNET_OK != + setup_globals (cfg)) + { + global_ret = 1; + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + "exchangedb", + "IDLE_RESERVE_EXPIRATION_TIME", + &idle_reserve_expiration_time)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchangedb", + "IDLE_RESERVE_EXPIRATION_TIME"); + global_ret = 1; + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting audit\n"); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_escrow_balance)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_irregular_recoups)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_withdraw_fee_income)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_balance_insufficient_loss)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_balance_summary_delta_plus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_balance_summary_delta_minus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_arithmetic_delta_plus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_arithmetic_delta_minus)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_balance_reserve_not_closed)); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (currency, + &total_bad_sig_loss)); + GNUNET_assert (NULL != + (report_row_inconsistencies = json_array ())); + GNUNET_assert (NULL != + (denomination_key_validity_withdraw_inconsistencies = + json_array ())); + GNUNET_assert (NULL != + (report_reserve_balance_summary_wrong_inconsistencies = + json_array ())); + GNUNET_assert (NULL != + (report_reserve_balance_insufficient_inconsistencies = + json_array ())); + GNUNET_assert (NULL != + (report_reserve_not_closed_inconsistencies = json_array ())); + GNUNET_assert (NULL != + (report_amount_arithmetic_inconsistencies = json_array ())); + GNUNET_assert (NULL != + (report_bad_sig_losses = json_array ())); + if (GNUNET_OK != + setup_sessions_and_run (&analyze_reserves, + NULL)) + { + global_ret = 1; + return; + } + report = json_pack ("{s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:o," + " s:o, s:o, s:o, s:o, s:I," + " s:I, s:I, s:I, s:I, s:I," + " s:I, s:I }", + /* blocks #1 */ + "reserve_balance_insufficient_inconsistencies", + report_reserve_balance_insufficient_inconsistencies, + /* Tested in test-auditor.sh #3 */ + "total_loss_balance_insufficient", + TALER_JSON_from_amount (&total_balance_insufficient_loss), + /* Tested in test-auditor.sh #3 */ + "reserve_balance_summary_wrong_inconsistencies", + report_reserve_balance_summary_wrong_inconsistencies, + "total_balance_summary_delta_plus", + TALER_JSON_from_amount ( + &total_balance_summary_delta_plus), + "total_balance_summary_delta_minus", + TALER_JSON_from_amount ( + &total_balance_summary_delta_minus), + /* blocks #2 */ + "total_escrow_balance", + TALER_JSON_from_amount (&total_escrow_balance), + "total_withdraw_fee_income", + TALER_JSON_from_amount (&total_withdraw_fee_income), + /* Tested in test-auditor.sh #21 */ + "reserve_not_closed_inconsistencies", + report_reserve_not_closed_inconsistencies, + /* Tested in test-auditor.sh #21 */ + "total_balance_reserve_not_closed", + TALER_JSON_from_amount ( + &total_balance_reserve_not_closed), + /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ + "bad_sig_losses", + report_bad_sig_losses, + /* blocks #3 */ + /* Tested in test-auditor.sh #4/#5/#6/#7/#13 */ + "total_bad_sig_loss", + TALER_JSON_from_amount (&total_bad_sig_loss), + /* Tested in test-auditor.sh #14/#15 */ + "row_inconsistencies", + report_row_inconsistencies, + /* Tested in test-auditor.sh #23 */ + "denomination_key_validity_withdraw_inconsistencies", + denomination_key_validity_withdraw_inconsistencies, + "amount_arithmetic_inconsistencies", + report_amount_arithmetic_inconsistencies, + "total_arithmetic_delta_plus", + TALER_JSON_from_amount (&total_arithmetic_delta_plus), + /* blocks #4 */ + "total_arithmetic_delta_minus", + TALER_JSON_from_amount (&total_arithmetic_delta_minus), + "auditor_start_time", + json_from_time_abs (start_time), + "auditor_end_time", + json_from_time_abs (GNUNET_TIME_absolute_get ()), + "total_irregular_recoups", + TALER_JSON_from_amount (&total_irregular_recoups), + "start_ppr_reserve_in_serial_id", + (json_int_t) ppr_start.last_reserve_in_serial_id, + /* blocks #5 */ + "start_ppr_reserve_out_serial_id", + (json_int_t) ppr_start.last_reserve_out_serial_id, + "start_ppr_reserve_recoup_serial_id", + (json_int_t) ppr_start.last_reserve_recoup_serial_id, + "start_ppr_reserve_close_serial_id", + (json_int_t) ppr_start.last_reserve_close_serial_id, + "end_ppr_reserve_in_serial_id", + (json_int_t) ppr.last_reserve_in_serial_id, + "end_ppr_reserve_out_serial_id", + (json_int_t) ppr.last_reserve_out_serial_id, + /* blocks #6 */ + "end_ppr_reserve_recoup_serial_id", + (json_int_t) ppr.last_reserve_recoup_serial_id, + "end_ppr_reserve_close_serial_id", + (json_int_t) ppr.last_reserve_close_serial_id + ); + GNUNET_break (NULL != report); + finish_report (report); +} + + +/** + * The main function of the database initialization tool. + * Used to initialize the Taler Exchange's database. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_base32_auto ('m', + "exchange-key", + "KEY", + "public key of the exchange (Crockford base32 encoded)", + &master_pub), + GNUNET_GETOPT_option_flag ('r', + "restart", + "restart audit from the beginning (required on first run)", + &restart), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), + GNUNET_GETOPT_OPTION_END + }; + + /* force linker to link against libtalerutil; if we do + not do this, the linker may "optimize" libtalerutil + away and skip #TALER_OS_init(), which we do need */ + (void) TALER_project_data_default (); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-auditor-reserves", + "MESSAGE", + NULL)); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, + argv, + "taler-auditor-reserves", + "Audit Taler exchange reserve handling", + options, + &run, + NULL)) + return 1; + return global_ret; +} + + +/* end of taler-auditor-reserves.c */ |