diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c index c58a8b151..b884e64bc 100644 --- a/src/auditor/taler-auditor.c +++ b/src/auditor/taler-auditor.c @@ -799,6 +799,89 @@ handle_reserve_out (void *cls, } +/** + * 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 payback request + * @param amount how much should be added back to the reserve + * @param reserve_pub public key of the reserve + * @param coin_pub public key of the coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_PAYBACK + * @param h_denom_pub hash of the denomination key of the coin + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop + */ +static int +handle_payback_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_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_DenominationBlindingKeyP *coin_blind) +{ + struct ReserveContext *rc = cls; + struct GNUNET_HashCode key; + struct ReserveSummary *rs; + struct GNUNET_TIME_Absolute expiry; + + /* should be monotonically increasing */ + GNUNET_assert (rowid >= pp.last_reserve_payback_serial_id); + pp.last_reserve_payback_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_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)); + if (GNUNET_OK != + load_auditor_reserve_summary (rs)) + { + GNUNET_break (0); + GNUNET_free (rs); + 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 /payback value to for reserve `%s' of %s\n", + TALER_B2S (reserve_pub), + TALER_amount2s (amount)); + expiry = GNUNET_TIME_absolute_add (timestamp, + TALER_IDLE_RESERVE_EXPIRATION_TIME); + rs->a_expiration_date = GNUNET_TIME_absolute_max (rs->a_expiration_date, + expiry); + 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. @@ -1041,7 +1124,20 @@ analyze_reserves (void *cls) GNUNET_break (0); return GNUNET_SYSERR; } - /* TODO: iterate over table for reserve expiration refunds! (#4956) */ + if (GNUNET_SYSERR == + edb->select_payback_above_serial_id (edb->cls, + esession, + pp.last_reserve_payback_serial_id, + &handle_payback_by_reserve, + &rc)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + + /* TODO: iterate over table for reserve expiration refunds! (#4956); + should use pp.last_reserve_close_serial_id */ GNUNET_CONTAINER_multihashmap_iterate (rc.reserves, &verify_reserve_balance, @@ -1774,8 +1870,9 @@ get_wire_fee (struct AggregationContext *ac, * @param wtid wire transfer subject * @param wire wire transfer details of the receiver * @param amount amount that was wired + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration */ -static void +static int check_wire_out_cb (void *cls, uint64_t rowid, struct GNUNET_TIME_Absolute date, @@ -1809,7 +1906,7 @@ check_wire_out_cb (void *cls, report_row_inconsistency ("wire_out", rowid, "specified wire address lacks type"); - return; + return GNUNET_OK; } wcc.method = json_string_value (method); wcc.ok = GNUNET_OK; @@ -1828,7 +1925,7 @@ check_wire_out_cb (void *cls, report_row_inconsistency ("wire_out", rowid, "audit of associated transactions failed"); - return; + return GNUNET_OK; } /* Subtract aggregation fee from total */ @@ -1839,7 +1936,7 @@ check_wire_out_cb (void *cls, { GNUNET_break (0); ac->ret = GNUNET_SYSERR; - return; + return GNUNET_SYSERR; } if (GNUNET_SYSERR == TALER_amount_subtract (&final_amount, @@ -1849,7 +1946,7 @@ check_wire_out_cb (void *cls, report_row_inconsistency ("wire_out", rowid, "could not subtract wire fee from total amount"); - return; + return GNUNET_OK; } /* Round down to amount supported by wire method */ @@ -1860,7 +1957,7 @@ check_wire_out_cb (void *cls, report_row_inconsistency ("wire_out", rowid, "could not load required wire plugin to validate"); - return; + return GNUNET_OK; } if (GNUNET_SYSERR == @@ -1880,7 +1977,7 @@ check_wire_out_cb (void *cls, { GNUNET_break (0); ac->ret = GNUNET_SYSERR; - return; + return GNUNET_SYSERR; } /* Sum up aggregation fees (we simply include the rounding gains) */ @@ -1891,7 +1988,7 @@ check_wire_out_cb (void *cls, { GNUNET_break (0); ac->ret = GNUNET_SYSERR; - return; + return GNUNET_SYSERR; } /* Check that calculated amount matches actual amount */ @@ -1903,11 +2000,12 @@ check_wire_out_cb (void *cls, &final_amount, amount, "computed amount inconsistent with wire amount"); - return; + return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Wire transfer %s is OK\n", TALER_B2S (wtid)); + return GNUNET_OK; } diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c index 380763904..b68708f0c 100644 --- a/src/auditordb/plugin_auditordb_postgres.c +++ b/src/auditordb/plugin_auditordb_postgres.c @@ -301,6 +301,8 @@ postgres_create_tables (void *cls) "(master_pub BYTEA PRIMARY KEY CHECK (LENGTH(master_pub)=32)" ",last_reserve_in_serial_id INT8 NOT NULL" ",last_reserve_out_serial_id INT8 NOT NULL" + ",last_reserve_payback_serial_id INT8 NOT NULL" + ",last_reserve_close_serial_id INT8 NOT NULL" ",last_withdraw_serial_id INT8 NOT NULL" ",last_deposit_serial_id INT8 NOT NULL" ",last_melt_serial_id INT8 NOT NULL" @@ -575,32 +577,38 @@ postgres_prepare (PGconn *db_conn) "(master_pub" ",last_reserve_in_serial_id" ",last_reserve_out_serial_id" + ",last_reserve_payback_serial_id" + ",last_reserve_close_serial_id" ",last_withdraw_serial_id" ",last_deposit_serial_id" ",last_melt_serial_id" ",last_refund_serial_id" ",last_wire_out_serial_id" - ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8);", - 8, NULL); + ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);", + 10, NULL); /* Used in #postgres_update_auditor_progress() */ PREPARE ("auditor_progress_update", "UPDATE auditor_progress SET " " last_reserve_in_serial_id=$1" ",last_reserve_out_serial_id=$2" - ",last_withdraw_serial_id=$3" - ",last_deposit_serial_id=$4" - ",last_melt_serial_id=$5" - ",last_refund_serial_id=$6" - ",last_wire_out_serial_id=$7" - " WHERE master_pub=$8", - 8, NULL); + ",last_reserve_payback_serial_id=$3" + ",last_reserve_close_serial_id=$4" + ",last_withdraw_serial_id=$5" + ",last_deposit_serial_id=$6" + ",last_melt_serial_id=$7" + ",last_refund_serial_id=$8" + ",last_wire_out_serial_id=$9" + " WHERE master_pub=$10", + 10, NULL); /* Used in #postgres_get_auditor_progress() */ PREPARE ("auditor_progress_select", "SELECT" " last_reserve_in_serial_id" ",last_reserve_out_serial_id" + ",last_reserve_payback_serial_id" + ",last_reserve_close_serial_id" ",last_withdraw_serial_id" ",last_deposit_serial_id" ",last_melt_serial_id" @@ -1310,6 +1318,8 @@ postgres_insert_auditor_progress (void *cls, GNUNET_PQ_query_param_auto_from_type (master_pub), GNUNET_PQ_query_param_uint64 (&pp->last_reserve_in_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_reserve_out_serial_id), + GNUNET_PQ_query_param_uint64 (&pp->last_reserve_payback_serial_id), + GNUNET_PQ_query_param_uint64 (&pp->last_reserve_close_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_withdraw_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_deposit_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_melt_serial_id), @@ -1356,6 +1366,8 @@ postgres_update_auditor_progress (void *cls, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&pp->last_reserve_in_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_reserve_out_serial_id), + GNUNET_PQ_query_param_uint64 (&pp->last_reserve_payback_serial_id), + GNUNET_PQ_query_param_uint64 (&pp->last_reserve_close_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_withdraw_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_deposit_serial_id), GNUNET_PQ_query_param_uint64 (&pp->last_melt_serial_id), @@ -1405,13 +1417,24 @@ postgres_get_auditor_progress (void *cls, }; PGresult *result; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("last_reserve_in_serial_id", &pp->last_reserve_in_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_reserve_out_serial_id", &pp->last_reserve_out_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_withdraw_serial_id", &pp->last_withdraw_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_deposit_serial_id", &pp->last_deposit_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_melt_serial_id", &pp->last_melt_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_refund_serial_id", &pp->last_refund_serial_id), - GNUNET_PQ_result_spec_uint64 ("last_wire_out_serial_id", &pp->last_wire_out_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_reserve_in_serial_id", + &pp->last_reserve_in_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_reserve_out_serial_id", + &pp->last_reserve_out_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_reserve_payback_serial_id", + &pp->last_reserve_payback_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_reserve_close_serial_id", + &pp->last_reserve_out_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_withdraw_serial_id", + &pp->last_withdraw_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_deposit_serial_id", + &pp->last_deposit_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_melt_serial_id", + &pp->last_melt_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_refund_serial_id", + &pp->last_refund_serial_id), + GNUNET_PQ_result_spec_uint64 ("last_wire_out_serial_id", + &pp->last_wire_out_serial_id), GNUNET_PQ_result_spec_end }; diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index e869bcd95..498b84864 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -515,7 +515,8 @@ postgres_create_tables (void *cls) /* Table for /payback information */ SQLEXEC("CREATE TABLE IF NOT EXISTS payback " - "(reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + "(payback_uuid BIGSERIAL" + ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE" ",coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)" ",coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)" ",coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)" @@ -1418,6 +1419,26 @@ postgres_prepare (PGconn *db_conn) "($1, $2, $3, $4, $5, $6, $7, $8, $9)", 9, NULL); + /* Used in #postgres_select_payback_above_serial_id() to obtain payback transactions */ + PREPARE ("payback_get_incr", + "SELECT" + " payback_uuid" + ",timestamp" + ",reserve_pub" + ",coin_pub" + ",coin_sig" + ",coin_blind" + ",h_blind_ev" + ",denom.denom_pub" + ",amount_val" + ",amount_frac" + ",amount_curr" + " FROM payback" + " JOIN reserves_out denom USING (reserve_pub,h_blind_ev)" + " WHERE payback_uuid>=$1" + " ORDER BY payback_uuid ASC", + 1, NULL); + /* Used in #postgres_get_reserve_history() to obtain payback transactions for a reserve */ PREPARE ("payback_by_reserve", @@ -5057,7 +5078,7 @@ postgres_select_deposits_above_serial_id (void *cls, return GNUNET_SYSERR; } int nrows; - int i; + int ret; nrows = PQntuples (result); if (0 == nrows) @@ -5067,7 +5088,7 @@ postgres_select_deposits_above_serial_id (void *cls, PQclear (result); return GNUNET_NO; } - for (i=0;iconn, "audit_get_refresh_sessions_incr", params); @@ -5164,8 +5191,6 @@ postgres_select_refreshs_above_serial_id (void *cls, PQclear (result); return GNUNET_SYSERR; } - int nrows; - int i; nrows = PQntuples (result); if (0 == nrows) @@ -5215,16 +5240,18 @@ postgres_select_refreshs_above_serial_id (void *cls, PQclear (result); return GNUNET_SYSERR; } - cb (cb_cls, - rowid, - &denom_pub, - &coin_pub, - &coin_sig, - &amount_with_fee, - num_newcoins, - noreveal_index, - &session_hash); + ret = cb (cb_cls, + rowid, + &denom_pub, + &coin_pub, + &coin_sig, + &amount_with_fee, + num_newcoins, + noreveal_index, + &session_hash); GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != ret) + break; } PQclear (result); return GNUNET_OK; @@ -5255,6 +5282,9 @@ postgres_select_refunds_above_serial_id (void *cls, GNUNET_PQ_query_param_end }; PGresult *result; + int nrows; + int ret; + result = GNUNET_PQ_exec_prepared (session->conn, "audit_get_refunds_incr", params); @@ -5265,8 +5295,6 @@ postgres_select_refunds_above_serial_id (void *cls, PQclear (result); return GNUNET_SYSERR; } - int nrows; - int i; nrows = PQntuples (result); if (0 == nrows) @@ -5276,7 +5304,7 @@ postgres_select_refunds_above_serial_id (void *cls, PQclear (result); return GNUNET_NO; } - for (i=0;iconn, "audit_get_wire_incr", params); @@ -5558,7 +5595,6 @@ postgres_select_wire_out_above_serial_id (void *cls, PQclear (result); return GNUNET_SYSERR; } - int nrows; nrows = PQntuples (result); if (0 == nrows) @@ -5600,13 +5636,126 @@ postgres_select_wire_out_above_serial_id (void *cls, return GNUNET_SYSERR; } - cb (cb_cls, - rowid, - date, - &wtid, - wire, - &amount); + ret = cb (cb_cls, + rowid, + date, + &wtid, + wire, + &amount); GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != ret) + break; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Function called to select payback requests the exchange + * received, ordered by serial ID (monotonically increasing). + * + * @param cls closure + * @param session database connection + * @param serial_id lowest serial ID to include (select larger or equal) + * @param cb function to call for ONE unfinished item + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, + * #GNUNET_NO if there are no entries, + * #GNUNET_SYSERR on DB errors + */ +static int +postgres_select_payback_above_serial_id (void *cls, + struct TALER_EXCHANGEDB_Session *session, + uint64_t serial_id, + TALER_EXCHANGEDB_PaybackCallback cb, + void *cb_cls) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&serial_id), + GNUNET_PQ_query_param_end + }; + PGresult *result; + result = GNUNET_PQ_exec_prepared (session->conn, + "payback_get_incr", + params); + if (PGRES_TUPLES_OK != + PQresultStatus (result)) + { + BREAK_DB_ERR (result, session->conn); + PQclear (result); + return GNUNET_SYSERR; + } + int nrows; + int ret; + + nrows = PQntuples (result); + if (0 == nrows) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "select_prepare_above_serial_id() returned 0 matching rows\n"); + PQclear (result); + return GNUNET_NO; + } + for (int i=0;iselect_reserves_in_above_serial_id = &postgres_select_reserves_in_above_serial_id; plugin->select_reserves_out_above_serial_id = &postgres_select_reserves_out_above_serial_id; plugin->select_wire_out_above_serial_id = &postgres_select_wire_out_above_serial_id; + plugin->select_payback_above_serial_id = &postgres_select_payback_above_serial_id; plugin->insert_payback_request = &postgres_insert_payback_request; plugin->get_reserve_by_h_blind = &postgres_get_reserve_by_h_blind; return plugin; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index e8fd21194..49cc64316 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1205,8 +1205,9 @@ static struct TALER_Amount wire_out_amount; * @param wtid wire transfer subject * @param wire wire transfer details of the receiver * @param amount amount that was wired + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration */ -static void +static int audit_wire_cb (void *cls, uint64_t rowid, struct GNUNET_TIME_Absolute date, @@ -1223,6 +1224,7 @@ audit_wire_cb (void *cls, &wire_out_wtid, sizeof (*wtid))); GNUNET_assert (date.abs_value_us == wire_out_date.abs_value_us); + return GNUNET_OK; } diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h index 031df58cb..170a68fdd 100644 --- a/src/include/taler_auditordb_plugin.h +++ b/src/include/taler_auditordb_plugin.h @@ -121,6 +121,18 @@ struct TALER_AUDITORDB_ProgressPoint */ uint64_t last_reserve_out_serial_id; + /** + * last_payback_serial_id serial ID of the last payback entry the auditor processed when + * considering reserves. + */ + uint64_t last_reserve_payback_serial_id; + + /** + * last_reserve_close_serial_id serial ID of the last reserve_close + * entry the auditor processed. + */ + uint64_t last_reserve_close_serial_id; + /** * last_reserve_out_serial_id serial ID of the last withdraw the auditor processed */ diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 322a30524..f6d78aba9 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -875,8 +875,9 @@ typedef void * @param wtid wire transfer subject * @param wire wire transfer details of the receiver * @param amount amount that was wired + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration */ -typedef void +typedef int (*TALER_EXCHANGEDB_WireTransferOutCallback)(void *cls, uint64_t rowid, struct GNUNET_TIME_Absolute date, @@ -894,8 +895,9 @@ typedef void * @param buf transaction data that was persisted, NULL on error * @param buf_size number of bytes in @a buf, 0 on error * @param finished did we complete the transfer yet? + * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration */ -typedef void +typedef int (*TALER_EXCHANGEDB_WirePreparationCallback)(void *cls, uint64_t rowid, const char *wire_method, @@ -909,18 +911,25 @@ typedef void * * @param cls closure * @param rowid row identifier used to uniquely identify the payback operation - * @param deadline by when did we promise the payment - * @param receiver_account_details to whom do we need to send the funds - * @param amount how much should be transferred - * @param wire_subject what should be the wire subject + * @param timestamp when did we receive the payback request + * @param amount how much should be added back to the reserve + * @param reserve_pub public key of the reserve + * @param coin_pub public key of the coin + * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_PAYBACK + * @param h_denom_pub hash of the denomination key of the coin + * @param coin_blind blinding factor used to blind the coin + * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop */ -typedef void +typedef int (*TALER_EXCHANGEDB_PaybackCallback)(void *cls, uint64_t rowid, - struct GNUNET_TIME_Absolute deadline, - const json_t *receiver_account_details, + struct GNUNET_TIME_Absolute timestamp, const struct TALER_Amount *amount, - const struct TALER_WireTransferIdentifierRawP *wtid); + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_DenominationBlindingKeyP *coin_blind); /** @@ -1939,6 +1948,27 @@ struct TALER_EXCHANGEDB_Plugin void *cb_cls); + /** + * Function called to select payback requests the exchange + * received, ordered by serial ID (monotonically increasing). + * + * @param cls closure + * @param session database connection + * @param serial_id lowest serial ID to include (select larger or equal) + * @param cb function to call for ONE unfinished item + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, + * #GNUNET_NO if there are no entries, + * #GNUNET_SYSERR on DB errors + */ + int + (*select_payback_above_serial_id)(void *cls, + struct TALER_EXCHANGEDB_Session *session, + uint64_t serial_id, + TALER_EXCHANGEDB_PaybackCallback cb, + void *cb_cls); + + /** * Function called to add a request for an emergency payback for a * coin. The funds are to be added back to the reserve. The