From 282e4f59ee849c6c84f753e8fae5691aaf8e9303 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 26 Jan 2020 17:48:13 +0100 Subject: address bank_api_credit comment/FIXME from Florian, also adapt debit logic accordingly --- src/bank-lib/bank_api_credit.c | 23 +++++++---------------- src/bank-lib/bank_api_debit.c | 30 ++++++++++++++---------------- 2 files changed, 21 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/bank-lib/bank_api_credit.c b/src/bank-lib/bank_api_credit.c index 50725a4ed..66e128da1 100644 --- a/src/bank-lib/bank_api_credit.c +++ b/src/bank-lib/bank_api_credit.c @@ -30,7 +30,7 @@ /** - * @brief A /history Handle + * @brief A /history/incoming Handle */ struct TALER_BANK_CreditHistoryHandle { @@ -173,30 +173,21 @@ handle_credit_history_finished (void *cls, GNUNET_break_op (0); ec = TALER_JSON_get_error_code (j); break; - case MHD_HTTP_FORBIDDEN: - /* Access denied */ - GNUNET_break_op (0); - ec = TALER_JSON_get_error_code (j); - break; case MHD_HTTP_UNAUTHORIZED: - /* FIXME(dold): I don't get this comment below. What signatures would the - bank even verify?! */ - /* Nothing really to verify, bank says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - GNUNET_break_op (0); + /* Nothing really to verify, bank says the HTTP Authentication + failed. May happen if HTTP authentication is used and the + user supplied a wrong username/password combination. */ ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - GNUNET_break_op (0); + /* Nothing really to verify: the bank is either unaware + of the endpoint (not a bank), or of the account. + We should pass the JSON (?) reply to the application */ ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ - GNUNET_break_op (0); ec = TALER_JSON_get_error_code (j); break; default: diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c index 0e218eb48..58f6ae6d4 100644 --- a/src/bank-lib/bank_api_debit.c +++ b/src/bank-lib/bank_api_debit.c @@ -30,7 +30,7 @@ /** - * @brief A /history Handle + * @brief A /history/outgoing Handle */ struct TALER_BANK_DebitHistoryHandle { @@ -129,16 +129,16 @@ parse_account_history (struct TALER_BANK_DebitHistoryHandle *hh, /** * Function called when we're done processing the - * HTTP /history request. + * HTTP /history/outgoing request. * * @param cls the `struct TALER_BANK_DebitHistoryHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ static void -handle_history_finished (void *cls, - long response_code, - const void *response) +handle_debit_history_finished (void *cls, + long response_code, + const void *response) { struct TALER_BANK_DebitHistoryHandle *hh = cls; enum TALER_ErrorCode ec; @@ -169,21 +169,19 @@ handle_history_finished (void *cls, case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the bank is buggy (or API version conflict); just pass JSON reply to the application */ - ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Access denied */ + GNUNET_break_op (0); ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_UNAUTHORIZED: - /* Nothing really to verify, bank says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ + /* Nothing really to verify, bank says the HTTP Authentication + failed. May happen if HTTP authentication is used and the + user supplied a wrong username/password combination. */ ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ + /* Nothing really to verify: the bank is either unaware + of the endpoint (not a bank), or of the account. + We should pass the JSON (?) reply to the application */ ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_INTERNAL_SERVER_ERROR: @@ -196,7 +194,7 @@ handle_history_finished (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u\n", (unsigned int) response_code); - GNUNET_break (0); + GNUNET_break_op (0); ec = TALER_JSON_get_error_code (j); response_code = 0; break; @@ -292,7 +290,7 @@ TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx, hh->job = GNUNET_CURL_job_add2 (ctx, eh, NULL, - &handle_history_finished, + &handle_debit_history_finished, hh); return hh; } -- cgit v1.2.3 From f4f86d2b0149231e95b81f12a607c670ad74f02f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 26 Jan 2020 18:51:25 +0100 Subject: increse log level on errors --- src/exchange/taler-exchange-wirewatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 21d96668b..972cee349 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -340,7 +340,7 @@ history_cb (void *cls, hh = NULL; if (TALER_EC_NONE != ec) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error fetching history: ec=%u, http_status=%u\n", (unsigned int) ec, http_status); -- cgit v1.2.3 From 232606fb57e3c74b62aa78d49225e2bb3c0c9ab4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 3 Feb 2020 23:42:47 +0100 Subject: add IF NOT EXISTS for indices --- src/auditordb/0001.sql | 4 +-- src/exchange/taler-exchange-aggregator.c | 8 ++--- src/exchangedb/0001.sql | 54 ++++++++++++++++---------------- src/json/json.c | 3 +- 4 files changed, 35 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/auditordb/0001.sql b/src/auditordb/0001.sql index 7f47f0357..3e666519c 100644 --- a/src/auditordb/0001.sql +++ b/src/auditordb/0001.sql @@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS auditor_reserves ,auditor_reserves_rowid BIGSERIAL UNIQUE ,origin_account TEXT ); -CREATE INDEX auditor_reserves_by_reserve_pub +CREATE INDEX IF NOT EXISTS auditor_reserves_by_reserve_pub ON auditor_reserves (reserve_pub); -- Table with the sum of the balances of all customer reserves @@ -203,7 +203,7 @@ CREATE TABLE IF NOT EXISTS auditor_historic_reserve_summary ,reserve_profits_val INT8 NOT NULL ,reserve_profits_frac INT4 NOT NULL ); -CREATE INDEX auditor_historic_reserve_summary_by_master_pub_start_date +CREATE INDEX IF NOT EXISTS auditor_historic_reserve_summary_by_master_pub_start_date ON auditor_historic_reserve_summary (master_pub ,start_date); diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c index a43277b23..5f57c8291 100644 --- a/src/exchange/taler-exchange-aggregator.c +++ b/src/exchange/taler-exchange-aggregator.c @@ -582,6 +582,7 @@ shutdown_task (void *cls) ctc = NULL; } TALER_EXCHANGEDB_plugin_unload (db_plugin); + db_plugin = NULL; { struct WireAccount *wa; @@ -670,6 +671,7 @@ parse_wirewatch_config () fprintf (stderr, "Failed to initialize DB tables\n"); TALER_EXCHANGEDB_plugin_unload (db_plugin); + db_plugin = NULL; return GNUNET_SYSERR; } TALER_EXCHANGEDB_find_accounts (cfg, @@ -680,6 +682,7 @@ parse_wirewatch_config () fprintf (stderr, "No wire accounts configured for debit!\n"); TALER_EXCHANGEDB_plugin_unload (db_plugin); + db_plugin = NULL; return GNUNET_SYSERR; } return GNUNET_OK; @@ -1179,10 +1182,7 @@ expired_reserve_cb (void *cls, /* Reserve balance was almost zero OR soft error */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Reserve was virtually empty, moving on\n"); - (void) commit_or_warn (ctc->session); - GNUNET_free (ctc->method); - GNUNET_free (ctc); - ctc = NULL; + (void) commit_or_warn (session); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); return qs; diff --git a/src/exchangedb/0001.sql b/src/exchangedb/0001.sql index 8e7ea0bf5..02dc68cf4 100644 --- a/src/exchangedb/0001.sql +++ b/src/exchangedb/0001.sql @@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS denominations ,fee_refund_val INT8 NOT NULL ,fee_refund_frac INT4 NOT NULL ); -CREATE INDEX denominations_expire_legal_index +CREATE INDEX IF NOT EXISTS denominations_expire_legal_index ON denominations (expire_legal); @@ -65,18 +65,18 @@ CREATE TABLE IF NOT EXISTS reserves ,gc_date INT8 NOT NULL ); -- index on reserves table (TODO: useless due to primary key!?) -CREATE INDEX reserves_reserve_pub_index +CREATE INDEX IF NOT EXISTS reserves_reserve_pub_index ON reserves (reserve_pub); -- index for get_expired_reserves -CREATE INDEX reserves_expiration_index +CREATE INDEX IF NOT EXISTS reserves_expiration_index ON reserves (expiration_date ,current_balance_val ,current_balance_frac ); -- index for reserve GC operations -CREATE INDEX reserves_gc_index +CREATE INDEX IF NOT EXISTS reserves_gc_index ON reserves (gc_date); -- reserves_in table collects the transactions which transfer funds @@ -94,12 +94,12 @@ CREATE TABLE IF NOT EXISTS reserves_in ,PRIMARY KEY (reserve_pub, wire_reference) ); -- Create indices on reserves_in -CREATE INDEX reserves_in_execution_index +CREATE INDEX IF NOT EXISTS reserves_in_execution_index ON reserves_in (exchange_account_section ,execution_date ); -CREATE INDEX reserves_in_exchange_account_serial +CREATE INDEX IF NOT EXISTS reserves_in_exchange_account_serial ON reserves_in (exchange_account_section, reserve_in_serial_id DESC @@ -116,7 +116,7 @@ CREATE TABLE IF NOT EXISTS reserves_close ,amount_frac INT4 NOT NULL ,closing_fee_val INT8 NOT NULL ,closing_fee_frac INT4 NOT NULL); -CREATE INDEX reserves_close_by_reserve +CREATE INDEX IF NOT EXISTS reserves_close_by_reserve ON reserves_close (reserve_pub); -- Table with the withdraw operations that have been performed on a reserve. @@ -137,13 +137,13 @@ CREATE TABLE IF NOT EXISTS reserves_out ,amount_with_fee_frac INT4 NOT NULL ); -- Index blindcoins(reserve_pub) for get_reserves_out statement -CREATE INDEX reserves_out_reserve_pub_index +CREATE INDEX IF NOT EXISTS reserves_out_reserve_pub_index ON reserves_out (reserve_pub); -CREATE INDEX reserves_out_execution_date +CREATE INDEX IF NOT EXISTS reserves_out_execution_date ON reserves_out (execution_date); -CREATE INDEX reserves_out_for_get_withdraw_info +CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info ON reserves_out (denom_pub_hash ,h_blind_ev @@ -155,7 +155,7 @@ CREATE TABLE IF NOT EXISTS known_coins ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE ,denom_sig BYTEA NOT NULL ); -CREATE INDEX known_coins_by_denomination +CREATE INDEX IF NOT EXISTS known_coins_by_denomination ON known_coins (denom_pub_hash); -- Table with the commitments made when melting a coin. */ @@ -168,7 +168,7 @@ CREATE TABLE IF NOT EXISTS refresh_commitments ,amount_with_fee_frac INT4 NOT NULL ,noreveal_index INT4 NOT NULL ); -CREATE INDEX refresh_commitments_old_coin_pub_index +CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index ON refresh_commitments (old_coin_pub); -- Table with the revelations about the new coins that are to be created @@ -188,7 +188,7 @@ CREATE TABLE IF NOT EXISTS refresh_revealed_coins ,PRIMARY KEY (rc, newcoin_index) ,UNIQUE (h_coin_ev) ); -CREATE INDEX refresh_revealed_coins_coin_pub_index +CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index ON refresh_revealed_coins (denom_pub_hash); -- Table with the transfer keys of a refresh operation; includes @@ -203,7 +203,7 @@ CREATE TABLE IF NOT EXISTS refresh_transfer_keys -- for get_link (not sure if this helps, as there should be very few -- transfer_pubs per rc, but at least in theory this helps the ORDER BY -- clause. -CREATE INDEX refresh_transfer_keys_coin_tpub +CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub ON refresh_transfer_keys (rc ,transfer_pub @@ -228,14 +228,14 @@ CREATE TABLE IF NOT EXISTS deposits ,UNIQUE (coin_pub, merchant_pub, h_contract_terms) ); -- Index for get_deposit_for_wtid and get_deposit_statement */ -CREATE INDEX deposits_coin_pub_merchant_contract_index +CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index ON deposits (coin_pub ,merchant_pub ,h_contract_terms ); -- Index for deposits_get_ready -CREATE INDEX deposits_get_ready_index +CREATE INDEX IF NOT EXISTS deposits_get_ready_index ON deposits (tiny ,done @@ -243,7 +243,7 @@ CREATE INDEX deposits_get_ready_index ,refund_deadline ); -- Index for deposits_iterate_matching -CREATE INDEX deposits_iterate_matching +CREATE INDEX IF NOT EXISTS deposits_iterate_matching ON deposits (merchant_pub ,h_wire @@ -265,7 +265,7 @@ CREATE TABLE IF NOT EXISTS refunds ,amount_with_fee_frac INT4 NOT NULL ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id) ); -CREATE INDEX refunds_coin_pub_index +CREATE INDEX IF NOT EXISTS refunds_coin_pub_index ON refunds (coin_pub); -- This table contains the data for @@ -287,7 +287,7 @@ CREATE TABLE IF NOT EXISTS aggregation_tracking ,wtid_raw BYTEA CONSTRAINT wire_out_ref REFERENCES wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE ); -- Index for lookup_transactions statement on wtid -CREATE INDEX aggregation_tracking_wtid_index +CREATE INDEX IF NOT EXISTS aggregation_tracking_wtid_index ON aggregation_tracking (wtid_raw); -- Table for the wire fees. @@ -302,7 +302,7 @@ CREATE TABLE IF NOT EXISTS wire_fee ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,PRIMARY KEY (wire_method, start_date) ); -CREATE INDEX wire_fee_gc_index +CREATE INDEX IF NOT EXISTS wire_fee_gc_index ON wire_fee (end_date); -- Table for /recoup information @@ -317,13 +317,13 @@ CREATE TABLE IF NOT EXISTS recoup ,timestamp INT8 NOT NULL ,h_blind_ev BYTEA NOT NULL REFERENCES reserves_out (h_blind_ev) ON DELETE CASCADE ); -CREATE INDEX recoup_by_coin_index +CREATE INDEX IF NOT EXISTS recoup_by_coin_index ON recoup (coin_pub); -CREATE INDEX recoup_by_h_blind_ev +CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev ON recoup (h_blind_ev); -CREATE INDEX recoup_for_by_reserve +CREATE INDEX IF NOT EXISTS recoup_for_by_reserve ON recoup (coin_pub ,h_blind_ev @@ -340,13 +340,13 @@ CREATE TABLE IF NOT EXISTS recoup_refresh ,timestamp INT8 NOT NULL ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE ); -CREATE INDEX recoup_refresh_by_coin_index +CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index ON recoup_refresh (coin_pub); -CREATE INDEX recoup_refresh_by_h_blind_ev +CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev ON recoup_refresh (h_blind_ev); -CREATE INDEX recoup_refresh_for_by_reserve +CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve ON recoup_refresh (coin_pub ,h_blind_ev @@ -360,7 +360,7 @@ CREATE TABLE IF NOT EXISTS prewire ,buf BYTEA NOT NULL ); -- Index for wire_prepare_data_get and gc_prewire statement -CREATE INDEX prepare_iteration_index +CREATE INDEX IF NOT EXISTS prepare_iteration_index ON prewire (finished); diff --git a/src/json/json.c b/src/json/json.c index 6de299319..f0c0aff57 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -81,7 +81,8 @@ TALER_JSON_get_error_code (const json_t *json) so we are dealing with a missing error code here. */ if (NULL == jc) { - GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected Taler error code `code' in JSON, but field does not exist!\n"); return TALER_EC_INVALID; } if (json_is_integer (jc)) -- cgit v1.2.3 From d47241e0e12ace8530ef4ecb2f9ac0f0918dcff6 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 4 Feb 2020 00:04:29 +0100 Subject: fix double continuation scheduling --- src/exchange/taler-exchange-aggregator.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src') diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c index 5f57c8291..a03eaff52 100644 --- a/src/exchange/taler-exchange-aggregator.c +++ b/src/exchange/taler-exchange-aggregator.c @@ -1183,6 +1183,8 @@ expired_reserve_cb (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Reserve was virtually empty, moving on\n"); (void) commit_or_warn (session); + erc->async_cont = GNUNET_YES; + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); return qs; @@ -1223,6 +1225,7 @@ expired_reserve_cb (void *cls, return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } erc->async_cont = GNUNET_YES; + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); GNUNET_free (ctc->method); @@ -1299,6 +1302,7 @@ run_reserve_closures (void *cls) case GNUNET_DB_STATUS_SOFT_ERROR: db_plugin->rollback (db_plugin->cls, session); + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_reserve_closures, NULL); return; @@ -1308,6 +1312,7 @@ run_reserve_closures (void *cls) reserves_idle = GNUNET_YES; db_plugin->rollback (db_plugin->cls, session); + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1315,6 +1320,7 @@ run_reserve_closures (void *cls) (void) commit_or_warn (session); if (GNUNET_YES == erc.async_cont) break; + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_reserve_closures, NULL); return; @@ -1345,6 +1351,7 @@ run_aggregation (void *cls) return; if (0 == (++swap % 2)) { + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_reserve_closures, NULL); return; @@ -1392,6 +1399,7 @@ run_aggregation (void *cls) { /* should re-try immediately */ swap--; /* do not count failed attempts */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1408,15 +1416,21 @@ run_aggregation (void *cls) { if ( (GNUNET_NO == reserves_idle) || (GNUNET_YES == test_mode) ) + { /* Possibly more to on reserves, go for it immediately */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_reserve_closures, NULL); + } else + { /* FIXME(dold): We might want to read the duration to sleep from the config */ /* nothing to do, sleep for a minute and try again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, &run_aggregation, NULL); + } } return; } @@ -1451,6 +1465,7 @@ run_aggregation (void *cls) "Serialization issue, trying again later!\n"); db_plugin->rollback (db_plugin->cls, session); + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1515,6 +1530,7 @@ run_aggregation (void *cls) session); cleanup_au (); /* start again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1531,6 +1547,7 @@ run_aggregation (void *cls) (void) commit_or_warn (session); cleanup_au (); /* start again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1587,6 +1604,7 @@ run_aggregation (void *cls) db_plugin->rollback (db_plugin->cls, session); /* start again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1613,6 +1631,7 @@ run_aggregation (void *cls) /* try again */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Commit issue for prepared wire data; trying again later!\n"); + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1625,6 +1644,7 @@ run_aggregation (void *cls) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Preparation complete, switching to transfer mode\n"); /* run alternative task: actually do wire transfer! */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); return; @@ -1685,6 +1705,7 @@ wire_confirm_cb (void *cls, if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { /* try again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); } @@ -1703,6 +1724,7 @@ wire_confirm_cb (void *cls, { case GNUNET_DB_STATUS_SOFT_ERROR: /* try again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1716,6 +1738,7 @@ wire_confirm_cb (void *cls, "Wire transfer complete\n"); /* continue with #run_transfers(), just to guard against the unlikely case that there are more. */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); return; @@ -1846,6 +1869,7 @@ run_transfers (void *cls) return; case GNUNET_DB_STATUS_SOFT_ERROR: /* try again */ + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); return; @@ -1853,6 +1877,7 @@ run_transfers (void *cls) /* no more prepared wire transfers, go back to aggregation! */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No more pending wire transfers, starting aggregation\n"); + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_aggregation, NULL); return; @@ -1898,6 +1923,7 @@ run (void *cls, return; } + GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&run_transfers, NULL); GNUNET_SCHEDULER_add_shutdown (&shutdown_task, -- cgit v1.2.3 From 5a11839002b2f327fa35e8a1bf589bb77c54e3c1 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 4 Feb 2020 15:47:57 +0100 Subject: add timetravel option for testing/debugging --- src/auditor/taler-auditor.c | 2 ++ src/auditor/taler-wire-auditor.c | 2 ++ src/exchange-tools/taler-exchange-keyup.c | 2 ++ src/exchange-tools/taler-exchange-wire.c | 2 ++ src/exchange/taler-exchange-aggregator.c | 2 ++ src/exchange/taler-exchange-httpd.c | 2 ++ src/exchange/taler-exchange-wirewatch.c | 4 +++- 7 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/auditor/taler-auditor.c b/src/auditor/taler-auditor.c index 4e32d2a4e..0ec5e2d75 100644 --- a/src/auditor/taler-auditor.c +++ b/src/auditor/taler-auditor.c @@ -5709,6 +5709,8 @@ main (int argc, "restart", "restart audit from the beginning (required on first run)", &restart), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), GNUNET_GETOPT_OPTION_END }; diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c index 94fd773c9..5a68f165d 100644 --- a/src/auditor/taler-wire-auditor.c +++ b/src/auditor/taler-wire-auditor.c @@ -2312,6 +2312,8 @@ main (int argc, "restart", "restart audit from the beginning (required on first run)", &restart), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), GNUNET_GETOPT_OPTION_END }; diff --git a/src/exchange-tools/taler-exchange-keyup.c b/src/exchange-tools/taler-exchange-keyup.c index 266c1bac0..f70ff23ac 100644 --- a/src/exchange-tools/taler-exchange-keyup.c +++ b/src/exchange-tools/taler-exchange-keyup.c @@ -1437,6 +1437,8 @@ main (int argc, "DKH", "revoke denomination key hash (DKH) and request wallets to initiate /recoup", &revoke_dkh), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), GNUNET_GETOPT_option_absolute_time ('t', "time", "TIMESTAMP", diff --git a/src/exchange-tools/taler-exchange-wire.c b/src/exchange-tools/taler-exchange-wire.c index 5ddc24040..0d6cdddbf 100644 --- a/src/exchange-tools/taler-exchange-wire.c +++ b/src/exchange-tools/taler-exchange-wire.c @@ -228,6 +228,8 @@ main (int argc, char *const *argv) { const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), GNUNET_GETOPT_option_filename ('m', "master-key", "FILENAME", diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c index a03eaff52..d380c3915 100644 --- a/src/exchange/taler-exchange-aggregator.c +++ b/src/exchange/taler-exchange-aggregator.c @@ -1943,6 +1943,8 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), GNUNET_GETOPT_option_flag ('t', "test", "run in test mode and exit when idle", diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 666cec0c1..426c48655 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -999,6 +999,8 @@ main (int argc, "SECONDS", "after how long do connections timeout by default (in seconds)", &connection_timeout), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), #if HAVE_DEVELOPER GNUNET_GETOPT_option_filename ('f', "file-input", diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c index 972cee349..69929e127 100644 --- a/src/exchange/taler-exchange-wirewatch.c +++ b/src/exchange/taler-exchange-wirewatch.c @@ -597,7 +597,9 @@ main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_option_flag ('T', + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), + GNUNET_GETOPT_option_flag ('t', "test", "run in test mode and exit when idle", &test_mode), -- cgit v1.2.3 From 9bfeec352b95873bc94b96947a8dd335838f3e8b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 4 Feb 2020 20:57:11 +0100 Subject: fix #6065 --- src/exchange/taler-exchange-httpd_mhd.c | 9 ++-- src/exchange/taler-exchange-httpd_refresh_melt.c | 2 +- src/exchange/taler-exchange-httpd_refresh_reveal.c | 2 +- src/exchange/taler-exchange-httpd_refund.c | 57 ++++++++-------------- src/exchange/taler-exchange-httpd_reserve_status.c | 2 +- .../taler-exchange-httpd_reserve_withdraw.c | 2 +- src/exchange/taler-exchange-httpd_responses.c | 2 +- src/include/taler_error_codes.h | 5 ++ 8 files changed, 34 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/exchange/taler-exchange-httpd_mhd.c b/src/exchange/taler-exchange-httpd_mhd.c index 53944b6a7..0f2ce033a 100644 --- a/src/exchange/taler-exchange-httpd_mhd.c +++ b/src/exchange/taler-exchange-httpd_mhd.c @@ -128,11 +128,10 @@ TEH_MHD_handler_send_json_pack_error (struct TEH_RequestHandler *rh, (void) connection_cls; (void) upload_data; (void) upload_data_size; - return TALER_MHD_reply_json_pack (connection, - rh->response_code, - "{s:s}", - "error", - rh->data); + return TALER_MHD_reply_with_error (connection, + rh->response_code, + TALER_EC_METHOD_INVALID, + rh->data); } diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c index 71200037d..c7dc700f7 100644 --- a/src/exchange/taler-exchange-httpd_refresh_melt.c +++ b/src/exchange/taler-exchange-httpd_refresh_melt.c @@ -69,7 +69,7 @@ reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, return TALER_MHD_reply_json_pack (connection, MHD_HTTP_CONFLICT, "{s:s, s:I, s:o, s:o, s:o, s:o, s:o}", - "error", + "hint", "insufficient funds", "code", (json_int_t) diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c index 3619f9a39..1e03c8e7d 100644 --- a/src/exchange/taler-exchange-httpd_refresh_reveal.c +++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c @@ -108,7 +108,7 @@ reply_refresh_reveal_mismatch (struct MHD_Connection *connection, return TALER_MHD_reply_json_pack (connection, MHD_HTTP_CONFLICT, "{s:s, s:I, s:o}", - "error", "commitment violation", + "hint", "commitment violation", "code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index 8c6e90302..8e24b9b47 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -82,28 +82,6 @@ reply_refund_success (struct MHD_Connection *connection, } -/** - * Generate generic refund failure message. All the details - * are in the @a response_code. The body can be empty. - * - * @param connection connection to the client - * @param response_code response code to generate - * @param ec taler error code to include - * @return MHD result code - */ -static int -reply_refund_failure (struct MHD_Connection *connection, - unsigned int response_code, - enum TALER_ErrorCode ec) -{ - return TALER_MHD_reply_json_pack (connection, - response_code, - "{s:s, s:I}", - "hint", "refund failure", - "code", (json_int_t) ec); -} - - /** * Generate refund conflict failure message. Returns the * transaction list @a tl with the details about the conflict. @@ -194,9 +172,10 @@ refund_transaction (void *cls, if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = reply_refund_failure (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_COIN_NOT_FOUND); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFUND_COIN_NOT_FOUND, + "database transaction failure"); return qs; } deposit_found = GNUNET_NO; @@ -319,9 +298,10 @@ refund_transaction (void *cls, GNUNET_break_op (0); /* currency mismatch */ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); - *mhd_ret = reply_refund_failure (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_CURRENCY_MISSMATCH); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_REFUND_CURRENCY_MISSMATCH, + "currencies involved do not match"); return GNUNET_DB_STATUS_HARD_ERROR; } @@ -353,9 +333,10 @@ refund_transaction (void *cls, /* money was already transferred to merchant, can no longer refund */ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); - *mhd_ret = reply_refund_failure (connection, - MHD_HTTP_GONE, - TALER_EC_REFUND_MERCHANT_ALREADY_PAID); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_GONE, + TALER_EC_REFUND_MERCHANT_ALREADY_PAID, + "money already sent to merchant"); return GNUNET_DB_STATUS_HARD_ERROR; } @@ -366,9 +347,10 @@ refund_transaction (void *cls, GNUNET_break_op (0); /* cannot refund more than original value */ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); - *mhd_ret = reply_refund_failure (connection, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_REFUND_INSUFFICIENT_FUNDS); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_PRECONDITION_FAILED, + TALER_EC_REFUND_INSUFFICIENT_FUNDS, + "refund requested exceeds original value"); return GNUNET_DB_STATUS_HARD_ERROR; } /* Check refund fee matches fee of denomination key! */ @@ -481,9 +463,10 @@ verify_and_execute_refund (struct MHD_Connection *connection, if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - return reply_refund_failure (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_REFUND_COIN_NOT_FOUND); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFUND_COIN_NOT_FOUND, + "denomination of coin to be refunded not found in DB"); } } diff --git a/src/exchange/taler-exchange-httpd_reserve_status.c b/src/exchange/taler-exchange-httpd_reserve_status.c index 89bf8b38a..e2d35aaec 100644 --- a/src/exchange/taler-exchange-httpd_reserve_status.c +++ b/src/exchange/taler-exchange-httpd_reserve_status.c @@ -164,7 +164,7 @@ TEH_RESERVE_handler_reserve_status (struct TEH_RequestHandler *rh, return TALER_MHD_reply_json_pack (connection, MHD_HTTP_NOT_FOUND, "{s:s, s:s, s:I}", - "error", "Reserve not found", + "hint", "Reserve not found", "parameter", "reserve_pub", "code", (json_int_t) diff --git a/src/exchange/taler-exchange-httpd_reserve_withdraw.c b/src/exchange/taler-exchange-httpd_reserve_withdraw.c index 86633cd98..9daad0a02 100644 --- a/src/exchange/taler-exchange-httpd_reserve_withdraw.c +++ b/src/exchange/taler-exchange-httpd_reserve_withdraw.c @@ -74,7 +74,7 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, return TALER_MHD_reply_json_pack (connection, MHD_HTTP_CONFLICT, "{s:s, s:I, s:o, s:o}", - "error", "Insufficient funds", + "hint", "insufficient funds", "code", (json_int_t) TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS, diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index c88a8a254..90ca14c88 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -446,7 +446,7 @@ TEH_RESPONSE_reply_coin_insufficient_funds (struct MHD_Connection *connection, return TALER_MHD_reply_json_pack (connection, MHD_HTTP_CONFLICT, "{s:s, s:I, s:o}", - "error", "insufficient funds", + "hint", "insufficient funds", "code", (json_int_t) ec, "history", history); } diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index ac7f92888..917ac36db 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -80,6 +80,11 @@ enum TALER_ErrorCode */ TALER_EC_JSON_ALLOCATION_FAILURE = 7, + /** + * HTTP method invalid for this URL. + */ + TALER_EC_METHOD_INVALID = 8, + /** * The exchange failed to even just initialize its connection to the * database. This response is provided with HTTP status code -- cgit v1.2.3 From e6d6987e5685b35f6c3137f59894a02a63d09766 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 4 Feb 2020 21:49:18 +0100 Subject: capitalization of option changed --- src/testing/testing_api_cmd_exec_wirewatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/testing/testing_api_cmd_exec_wirewatch.c b/src/testing/testing_api_cmd_exec_wirewatch.c index 44de96838..7a1a27a51 100644 --- a/src/testing/testing_api_cmd_exec_wirewatch.c +++ b/src/testing/testing_api_cmd_exec_wirewatch.c @@ -67,7 +67,7 @@ wirewatch_run (void *cls, "taler-exchange-wirewatch", "taler-exchange-wirewatch", "-c", ws->config_filename, - "-T", /* exit when done */ + "-t", /* exit when done */ NULL); if (NULL == ws->wirewatch_proc) { -- cgit v1.2.3 From 42bc31744b9810509aef344c54bfee2f4e2a7ccb Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 4 Feb 2020 21:59:43 +0100 Subject: implement /config in fakebank and taler_bank_lib.h (#6066) --- src/bank-lib/Makefile.am | 1 + src/bank-lib/bank_api_config.c | 255 +++++++++++++++++++++++++++ src/bank-lib/fakebank.c | 63 ++++++- src/bank-lib/taler-fakebank-run.c | 28 ++- src/benchmark/taler-exchange-benchmark.c | 36 ++-- src/include/taler_bank_service.h | 73 +++++++- src/include/taler_fakebank_lib.h | 4 +- src/include/taler_testing_lib.h | 8 +- src/testing/test_bank_api.c | 36 +++- src/testing/test_taler_exchange_aggregator.c | 78 ++++---- src/testing/testing_api_helpers_bank.c | 7 +- src/testing/testing_api_helpers_exchange.c | 2 +- src/testing/testing_api_loop.c | 88 +++++---- 13 files changed, 564 insertions(+), 115 deletions(-) create mode 100644 src/bank-lib/bank_api_config.c (limited to 'src') diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 729f96e15..7227ad068 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -38,6 +38,7 @@ libtalerbank_la_LDFLAGS = \ libtalerbank_la_SOURCES = \ bank_api_admin.c \ bank_api_common.c bank_api_common.h \ + bank_api_config.c \ bank_api_credit.c \ bank_api_debit.c \ bank_api_transfer.c \ diff --git a/src/bank-lib/bank_api_config.c b/src/bank-lib/bank_api_config.c new file mode 100644 index 000000000..a84e4ff85 --- /dev/null +++ b/src/bank-lib/bank_api_config.c @@ -0,0 +1,255 @@ +/* + This file is part of TALER + Copyright (C) 2017--2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or + modify it under the terms of the GNU General 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 General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file bank-lib/bank_api_config.c + * @brief Implementation of the /config request + * @author Christian Grothoff + */ +#include "platform.h" +#include "bank_api_common.h" +#include /* just for HTTP status codes */ +#include "taler_signatures.h" + +/** + * Protocol version we implement. + */ +#define BANK_PROTOCOL_CURRENT 0 + +/** + * How many revisions back are we compatible to. + */ +#define BANK_PROTOCOL_AGE 0 + + +/** + * @brief A /config Handle + */ +struct TALER_BANK_ConfigHandle +{ + + /** + * The url for this request. + */ + char *request_url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_BANK_ConfigCallback hcb; + + /** + * Closure for @a cb. + */ + void *hcb_cls; +}; + + +/** + * Parse configuration given in JSON format and invoke the callback on each item. + * + * @param ch handle to the account configuration request + * @param config JSON object with the configuration + * @return #GNUNET_OK if configuration was valid and @a rconfiguration and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a configuration + */ +static int +parse_config (struct TALER_BANK_ConfigHandle *ch, + const json_t *config) +{ + struct TALER_BANK_Configuration cfg; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &cfg.version), + GNUNET_JSON_spec_string ("currency", + &cfg.version), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (config, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ch->hcb (ch->hcb_cls, + MHD_HTTP_OK, + TALER_EC_NONE, + &cfg); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /config request. + * + * @param cls the `struct TALER_BANK_ConfigHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_configuration_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_BANK_ConfigHandle *ch = cls; + enum TALER_ErrorCode ec; + const json_t *j = response; + + ch->job = NULL; + switch (response_code) + { + case 0: + ec = TALER_EC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_config (ch, + j)) + { + GNUNET_break_op (0); + response_code = 0; + ec = TALER_EC_INVALID_RESPONSE; + break; + } + response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */ + ec = TALER_EC_NONE; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the bank is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break_op (0); + ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, bank says the HTTP Authentication + failed. May happen if HTTP authentication is used and the + user supplied a wrong username/password combination. */ + ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + ec = TALER_JSON_get_error_code (j); + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break_op (0); + ec = TALER_JSON_get_error_code (j); + response_code = 0; + break; + } + ch->hcb (ch->hcb_cls, + response_code, + ec, + NULL); + TALER_BANK_configuration_cancel (ch); +} + + +/** + * Request the configuration of the bank. + * + * @param ctx curl context for the event loop + * @param auth authentication data to use + * @param hres_cb the callback to call with the + * configuration + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid + */ +struct TALER_BANK_ConfigHandle * +TALER_BANK_configuration (struct GNUNET_CURL_Context *ctx, + const struct TALER_BANK_AuthenticationData *auth, + TALER_BANK_ConfigCallback hres_cb, + void *hres_cb_cls) +{ + struct TALER_BANK_ConfigHandle *ch; + CURL *eh; + + ch = GNUNET_new (struct TALER_BANK_ConfigHandle); + ch->hcb = hres_cb; + ch->hcb_cls = hres_cb_cls; + ch->request_url = TALER_url_join (auth->wire_gateway_url, + "config", + NULL); + if (NULL == ch->request_url) + { + GNUNET_free (ch); + GNUNET_break (0); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting configuration at `%s'\n", + ch->request_url); + eh = curl_easy_init (); + if ( (GNUNET_OK != + TALER_BANK_setup_auth_ (eh, + auth)) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_URL, + ch->request_url)) ) + { + GNUNET_break (0); + TALER_BANK_configuration_cancel (ch); + curl_easy_cleanup (eh); + return NULL; + } + ch->job = GNUNET_CURL_job_add2 (ctx, + eh, + NULL, + &handle_configuration_finished, + ch); + return ch; +} + + +/** + * Cancel a configuration request. This function cannot be + * used on a request handle if a response is already + * served for it. + * + * @param ch the configuration request handle + */ +void +TALER_BANK_configuration_cancel (struct TALER_BANK_ConfigHandle *ch) +{ + if (NULL != ch->job) + { + GNUNET_CURL_job_cancel (ch->job); + ch->job = NULL; + } + GNUNET_free (ch->request_url); + GNUNET_free (ch); +} + + +/* end of bank_api_config.c */ diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index abf486876..56af11f8a 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -26,6 +26,24 @@ #include "taler_bank_service.h" #include "taler_mhd_lib.h" +/** + * Taler protocol version in the format CURRENT:REVISION:AGE + * as used by GNU libtool. See + * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html + * + * Please be very careful when updating and follow + * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info + * precisely. Note that this version has NOTHING to do with the + * release version, and the format is NOT the same that semantic + * versioning uses either. + * + * When changing this version, you likely want to also update + * #BANK_PROTOCOL_CURRENT and #BANK_PROTOCOL_AGE in + * bank_api_config.c! + */ +#define BANK_PROTOCOL_VERSION "0:0:0" + + /** * Maximum POST request size (for /admin/add-incoming) */ @@ -172,6 +190,11 @@ struct TALER_FAKEBANK_Handle */ uint64_t serial_counter; + /** + * Currency used by the fakebank. + */ + char *currency; + /** * BaseURL of the fakebank. */ @@ -527,6 +550,7 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) h->mhd_bank = NULL; } GNUNET_free (h->my_baseurl); + GNUNET_free (h->currency); GNUNET_free (h); } @@ -776,7 +800,7 @@ handle_transfer (struct TALER_FAKEBANK_Handle *h, /** - * Handle incoming HTTP request for /history + * Handle incoming HTTP request for / (home page). * * @param h the fakebank handle * @param connection the connection @@ -808,6 +832,29 @@ handle_home_page (struct TALER_FAKEBANK_Handle *h, } +/** + * Handle incoming HTTP request for /config + * + * @param h the fakebank handle + * @param connection the connection + * @param con_cls place to store state, not used + * @return MHD result code + */ +static int +handle_config (struct TALER_FAKEBANK_Handle *h, + struct MHD_Connection *connection, + void **con_cls) +{ + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s, s:s}", + "currency", + h->currency, + "version" + BANK_PROTOCOL_VERSION); +} + + /** * This is the "base" structure for both the /history and the * /history-range API calls. @@ -1202,6 +1249,13 @@ serve (struct TALER_FAKEBANK_Handle *h, return handle_home_page (h, connection, con_cls); + if ( (0 == strcmp (url, + "/config")) && + (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) ) + return handle_config (h, + connection, + con_cls); if ( (0 == strcmp (url, "/admin/add-incoming")) && (0 == strcasecmp (method, @@ -1433,15 +1487,19 @@ run_mhd (void *cls) * would have issued the correct wire transfer orders. * * @param port port to listen to + * @param currency currency the bank uses * @return NULL on error */ struct TALER_FAKEBANK_Handle * -TALER_FAKEBANK_start (uint16_t port) +TALER_FAKEBANK_start (uint16_t port, + const char *currency) { struct TALER_FAKEBANK_Handle *h; + GNUNET_assert (strlen (currency) < TALER_CURRENCY_LEN); h = GNUNET_new (struct TALER_FAKEBANK_Handle); h->port = port; + h->currency = GNUNET_strdup (currency); GNUNET_asprintf (&h->my_baseurl, "http://localhost:%u/", (unsigned int) port); @@ -1462,6 +1520,7 @@ TALER_FAKEBANK_start (uint16_t port) MHD_OPTION_END); if (NULL == h->mhd_bank) { + GNUNET_free (h->currency); GNUNET_free (h); return NULL; } diff --git a/src/bank-lib/taler-fakebank-run.c b/src/bank-lib/taler-fakebank-run.c index 55b3da54f..588777c94 100644 --- a/src/bank-lib/taler-fakebank-run.c +++ b/src/bank-lib/taler-fakebank-run.c @@ -43,12 +43,38 @@ run (void *cls, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) { + char *currency_string; + (void) cls; (void) args; (void) cfgfile; (void) cfg; - if (NULL == TALER_FAKEBANK_start (8082)) + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "taler", + "CURRENCY", + ¤cy_string)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler", + "CURRENCY"); + ret = 1; + return; + } + if (strlen (currency_string) >= TALER_CURRENCY_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "taler", + "CURRENCY", + "Value is too long"); + GNUNET_free (currency_string); + ret = 1; + return; + } + if (NULL == TALER_FAKEBANK_start (8082, + currency_string)) ret = 1; + GNUNET_free (currency_string); ret = 0; } diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c index bd4d3eafe..91ed5b047 100644 --- a/src/benchmark/taler-exchange-benchmark.c +++ b/src/benchmark/taler-exchange-benchmark.c @@ -156,9 +156,9 @@ static char *mode_str; static enum BenchmarkMode mode; /** - * Config filename. + * Configuration. */ -static char *cfg_filename; +static struct GNUNET_CONFIGURATION_Handle *cfg; /** * Currency used. @@ -445,7 +445,8 @@ launch_fakebank (void *cls) (void) cls; fakebank - = TALER_TESTING_run_fakebank (exchange_bank_account.wire_gateway_url); + = TALER_TESTING_run_fakebank (exchange_bank_account.wire_gateway_url, + currency); if (NULL == fakebank) { GNUNET_break (0); @@ -624,12 +625,11 @@ parallel_benchmark (TALER_TESTING_Main main_cb, NULL == loglev ? "INFO" : loglev, logfile); - result = TALER_TESTING_setup - (main_cb, - main_cb_cls, - cfg_filename, - exchanged, - GNUNET_YES); + result = TALER_TESTING_setup (main_cb, + main_cb_cls, + cfg, + exchanged, + GNUNET_YES); if (GNUNET_OK != result) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failure in child process test suite!\n"); @@ -746,7 +746,7 @@ int main (int argc, char *const *argv) { - struct GNUNET_CONFIGURATION_Handle *cfg; + char *cfg_filename = NULL; struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_mandatory (GNUNET_GETOPT_option_cfgfile (&cfg_filename)), @@ -798,6 +798,7 @@ main (int argc, argc, argv))) { + GNUNET_free_non_null (cfg_filename); return BAD_CLI_ARG; } GNUNET_log_setup ("taler-exchange-benchmark", @@ -814,6 +815,7 @@ main (int argc, else { TALER_LOG_ERROR ("Unknown mode given: '%s'\n", mode_str); + GNUNET_free_non_null (cfg_filename); return BAD_CONFIG_FILE; } if (NULL == cfg_filename) @@ -825,8 +827,10 @@ main (int argc, cfg_filename)) { TALER_LOG_ERROR ("Could not parse configuration\n"); + GNUNET_free (cfg_filename); return BAD_CONFIG_FILE; } + GNUNET_free (cfg_filename); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", @@ -839,6 +843,16 @@ main (int argc, GNUNET_CONFIGURATION_destroy (cfg); return BAD_CONFIG_FILE; } + + if (strlen (currency) >= TALER_CURRENCY_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "taler", + "CURRENCY", + "Value is too long"); + GNUNET_CONFIGURATION_destroy (cfg); + return BAD_CONFIG_FILE; + } if (howmany_clients > 10240) { TALER_LOG_ERROR ("-p option value given is too large\n"); @@ -950,11 +964,11 @@ main (int argc, return BAD_CONFIG_FILE; } } - GNUNET_CONFIGURATION_destroy (cfg); result = parallel_benchmark (&run, NULL, cfg_filename); + GNUNET_CONFIGURATION_destroy (cfg); /* If we're the exchange worker, we're done now. No need to print results */ if (MODE_EXCHANGE == mode) diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h index 98a1017c2..c9c93fac5 100644 --- a/src/include/taler_bank_service.h +++ b/src/include/taler_bank_service.h @@ -89,6 +89,73 @@ struct TALER_BANK_AuthenticationData }; +/* ********************* /config *********************** */ + +/** + * @brief A /config Handle + */ +struct TALER_BANK_ConfigHandle; + +/** + * Configuration data provided by the bank. + */ +struct TALER_BANK_Configuration +{ + /** + * Current protocol version. Libtool style. + */ + const char *version; + + /** + * Currency used by the bank. + */ + const char *currency; +}; + + +/** + * Function called with configuration details from the bank. + * + * @param cls closure + * @param http status code + * @param ec taler error code + * @param config the configuration, NULL on error + */ +typedef void +(*TALER_BANK_ConfigCallback)(void *cls, + unsigned int http_status, + enum TALER_ErrorCode ec, + const struct TALER_BANK_Configuration *config); + +/** + * Request the configuration of the bank. + * + * @param ctx curl context for the event loop + * @param auth authentication data to use + * @param hres_cb the callback to call with the + * configuration + * @param hres_cb_cls closure for the above callback + * @return NULL if the inputs are invalid (i.e. zero value for + * @e num_results). In this case, the callback is not + * called. + */ +struct TALER_BANK_ConfigHandle * +TALER_BANK_configuration (struct GNUNET_CURL_Context *ctx, + const struct TALER_BANK_AuthenticationData *auth, + TALER_BANK_ConfigCallback hres_cb, + void *hres_cb_cls); + + +/** + * Cancel a configuration request. This function cannot be + * used on a request handle if a response is already + * served for it. + * + * @param ch the configuration request handle + */ +void +TALER_BANK_configuration_cancel (struct TALER_BANK_ConfigHandle *ch); + /* ********************* /admin/add/incoming *********************** */ @@ -160,7 +227,7 @@ TALER_BANK_admin_add_incoming_cancel (struct TALER_BANK_AdminAddIncomingHandle *aai); -/* ********************* /taler/transfer *********************** */ +/* ********************* /transfer *********************** */ /** * Prepare for exeuction of a wire transfer. @@ -243,7 +310,7 @@ TALER_BANK_execute_wire_transfer_cancel (struct TALER_BANK_WireExecuteHandle *weh); -/* ********************* /taler/credits *********************** */ +/* ********************* /history/incoming *********************** */ /** * Handle for querying the bank for transactions @@ -347,7 +414,7 @@ void TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh); -/* ********************* /taler/debits *********************** */ +/* ********************* /history/outgoing *********************** */ /** * Handle for querying the bank for transactions diff --git a/src/include/taler_fakebank_lib.h b/src/include/taler_fakebank_lib.h index 9324bdce1..8601a8d8d 100644 --- a/src/include/taler_fakebank_lib.h +++ b/src/include/taler_fakebank_lib.h @@ -44,10 +44,12 @@ struct TALER_FAKEBANK_Handle; * would have issued the correct wire transfer orders. * * @param port port to listen to + * @param currency which currency should the bank offer * @return NULL on error */ struct TALER_FAKEBANK_Handle * -TALER_FAKEBANK_start (uint16_t port); +TALER_FAKEBANK_start (uint16_t port, + const char *currency); /** diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 0b3d19999..5aa50155c 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -635,7 +635,7 @@ typedef void * @param main_cb the "run" method which coontains all the * commands. * @param main_cb_cls a closure for "run", typically NULL. - * @param config_filename configuration filename. + * @param cfg configuration to use * @param exchanged exchange process handle: will be put in the * state as some commands - e.g. revoke - need to send * signal to it, for example to let it know to reload the @@ -650,7 +650,7 @@ typedef void int TALER_TESTING_setup (TALER_TESTING_Main main_cb, void *main_cb_cls, - const char *config_filename, + const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_OS_Process *exchanged, int exchange_connect); @@ -789,11 +789,13 @@ TALER_TESTING_run_bank (const char *config_filename, * from the base URL. * * @param bank_url bank's base URL. + * @param currency currency the bank uses * @return the fakebank process handle, or NULL if any * error occurs. */ struct TALER_FAKEBANK_Handle * -TALER_TESTING_run_fakebank (const char *bank_url); +TALER_TESTING_run_fakebank (const char *bank_url, + const char *currency); /** diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c index bdafdc5fd..75e22b59e 100644 --- a/src/testing/test_bank_api.c +++ b/src/testing/test_bank_api.c @@ -52,6 +52,7 @@ static struct GNUNET_OS_Process *bankd; */ static int with_fakebank; + /** * Main function that will tell the interpreter what commands to * run. @@ -116,12 +117,32 @@ run (void *cls, } +/** + * Runs #TALER_TESTING_setup() using the configuration. + * + * @param cls unused + * @param cfg configuration to use + * @return status code + */ +static int +setup_with_cfg (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + (void) cls; + return TALER_TESTING_setup (&run, + NULL, + cfg, + NULL, + GNUNET_NO); +} + + int main (int argc, char *const *argv) { - int rv; const char *cfgfilename; + int rv; /* These environment variables get in the way... */ unsetenv ("XDG_DATA_HOME"); @@ -165,12 +186,13 @@ main (int argc, return 77; } } - - rv = (GNUNET_OK == TALER_TESTING_setup (&run, - NULL, - cfgfilename, - NULL, - GNUNET_NO)) ? 0 : 1; + if (GNUNET_OK != + GNUNET_CONFIGURATION_parse_and_run (cfgfilename, + &setup_with_cfg, + NULL)) + rv = 1; + else + rv = 0; if (GNUNET_NO == with_fakebank) { diff --git a/src/testing/test_taler_exchange_aggregator.c b/src/testing/test_taler_exchange_aggregator.c index c709e5fe9..2e8a35ce2 100644 --- a/src/testing/test_taler_exchange_aggregator.c +++ b/src/testing/test_taler_exchange_aggregator.c @@ -57,36 +57,6 @@ static char *config_filename; #define USER42_ACCOUNT "42" -/** - * @return GNUNET_NO if database could not be prepared, - * otherwise GNUNET_OK - */ -static int -prepare_database (void *cls, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - dbc.plugin = TALER_EXCHANGEDB_plugin_load (cfg); - if (NULL == dbc.plugin) - { - GNUNET_break (0); - result = 77; - return GNUNET_NO; - } - if (GNUNET_OK != - dbc.plugin->create_tables (dbc.plugin->cls)) - { - GNUNET_break (0); - TALER_EXCHANGEDB_plugin_unload (dbc.plugin); - dbc.plugin = NULL; - result = 77; - return GNUNET_NO; - } - dbc.session = dbc.plugin->get_session (dbc.plugin->cls); - GNUNET_assert (NULL != dbc.session); - - return GNUNET_OK; -} - /** * Collects all the tests. @@ -454,6 +424,48 @@ run (void *cls, } +/** + * Prepare database an launch the test. + * + * @param cls unused + * @param cfg our configuration + * @return #GNUNET_NO if database could not be prepared, + * otherwise #GNUNET_OK + */ +static int +prepare_database (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + dbc.plugin = TALER_EXCHANGEDB_plugin_load (cfg); + if (NULL == dbc.plugin) + { + GNUNET_break (0); + result = 77; + return GNUNET_NO; + } + if (GNUNET_OK != + dbc.plugin->create_tables (dbc.plugin->cls)) + { + GNUNET_break (0); + TALER_EXCHANGEDB_plugin_unload (dbc.plugin); + dbc.plugin = NULL; + result = 77; + return GNUNET_NO; + } + dbc.session = dbc.plugin->get_session (dbc.plugin->cls); + GNUNET_assert (NULL != dbc.session); + + result = TALER_TESTING_setup (&run, + NULL, + cfg, + NULL, // no exchange process handle. + GNUNET_NO); // do not try to connect to the exchange + + + return GNUNET_OK; +} + + int main (int argc, char *const argv[]) @@ -507,12 +519,6 @@ main (int argc, return result; } - result = TALER_TESTING_setup (&run, - NULL, - config_filename, - NULL, // no exchange process handle. - GNUNET_NO); // do not try to connect to the exchange - GNUNET_free (config_filename); GNUNET_free (testname); dbc.plugin->drop_tables (dbc.plugin->cls); diff --git a/src/testing/testing_api_helpers_bank.c b/src/testing/testing_api_helpers_bank.c index 7b8b203a3..35ef4792f 100644 --- a/src/testing/testing_api_helpers_bank.c +++ b/src/testing/testing_api_helpers_bank.c @@ -36,11 +36,13 @@ * from the base URL. * * @param bank_url bank's base URL. + * @param currency currency the bank uses * @return the fakebank process handle, or NULL if any * error occurs. */ struct TALER_FAKEBANK_Handle * -TALER_TESTING_run_fakebank (const char *bank_url) +TALER_TESTING_run_fakebank (const char *bank_url, + const char *currency) { const char *port; long pnum; @@ -56,7 +58,8 @@ TALER_TESTING_run_fakebank (const char *bank_url) "Starting Fakebank on port %u (%s)\n", (unsigned int) pnum, bank_url); - fakebankd = TALER_FAKEBANK_start ((uint16_t) pnum); + fakebankd = TALER_FAKEBANK_start ((uint16_t) pnum, + currency); if (NULL == fakebankd) { GNUNET_break (0); diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c index 29c96db19..911bc6f0e 100644 --- a/src/testing/testing_api_helpers_exchange.c +++ b/src/testing/testing_api_helpers_exchange.c @@ -788,7 +788,7 @@ TALER_TESTING_setup_with_exchange_cfg (void *cls, /* NOTE: this call blocks. */ result = TALER_TESTING_setup (setup_ctx->main_cb, setup_ctx->main_cb_cls, - setup_ctx->config_filename, + cfg, exchanged, GNUNET_YES); GNUNET_break (0 == diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c index 61b95c2e1..e9ccdb81c 100644 --- a/src/testing/testing_api_loop.c +++ b/src/testing/testing_api_loop.c @@ -124,7 +124,33 @@ TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is, struct TALER_TESTING_Command *commands, const char *bank_url) { - is->fakebank = TALER_TESTING_run_fakebank (bank_url); + char *currency; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (is->cfg, + "taler", + "CURRENCY", + ¤cy)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler", + "CURRENCY"); + is->result = GNUNET_SYSERR; + return; + } + if (strlen (currency) >= TALER_CURRENCY_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "taler", + "CURRENCY", + "Value is too long"); + GNUNET_free (currency); + is->result = GNUNET_SYSERR; + return; + } + is->fakebank = TALER_TESTING_run_fakebank (bank_url, + currency); + GNUNET_free (currency); if (NULL == is->fakebank) { GNUNET_break (0); @@ -529,14 +555,6 @@ struct MainContext */ struct TALER_TESTING_Interpreter *is; - /** - * Configuration filename. The wrapper uses it to fetch - * the exchange port number; We could have passed the port - * number here, but having the config filename seems more - * generic. - */ - const char *config_filename; - /** * URL of the exchange. */ @@ -683,18 +701,16 @@ do_abort (void *cls) * and responsible to run the "run" method. * * @param cls a `struct MainContext *` - * @param cfg configuration to use */ -static int -main_exchange_connect_with_cfg (void *cls, - const struct GNUNET_CONFIGURATION_Handle *cfg) +static void +main_wrapper_exchange_connect (void *cls) { struct MainContext *main_ctx = cls; struct TALER_TESTING_Interpreter *is = main_ctx->is; char *exchange_url; if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, + GNUNET_CONFIGURATION_get_value_string (is->cfg, "exchange", "BASE_URL", &exchange_url)) @@ -702,40 +718,18 @@ main_exchange_connect_with_cfg (void *cls, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange", "BASE_URL"); - return GNUNET_SYSERR; + return; } main_ctx->exchange_url = exchange_url; - is->cfg = cfg; is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort, main_ctx); GNUNET_break - (NULL != (is->exchange = TALER_EXCHANGE_connect - (is->ctx, - exchange_url, - &TALER_TESTING_cert_cb, - main_ctx, - TALER_EXCHANGE_OPTION_END))); - is->cfg = NULL; - return GNUNET_OK; -} - - -/** - * Initialize scheduler loop and curl context for the testcase, - * and responsible to run the "run" method. - * - * @param cls a `struct MainContext *` - */ -static void -main_wrapper_exchange_connect (void *cls) -{ - struct MainContext *main_ctx = cls; - - GNUNET_break (GNUNET_OK == - GNUNET_CONFIGURATION_parse_and_run (main_ctx->config_filename, - & - main_exchange_connect_with_cfg, - main_ctx)); + (NULL != (is->exchange = + TALER_EXCHANGE_connect (is->ctx, + exchange_url, + &TALER_TESTING_cert_cb, + main_ctx, + TALER_EXCHANGE_OPTION_END))); } @@ -746,7 +740,7 @@ main_wrapper_exchange_connect (void *cls) * @param main_cb the "run" method which contains all the * commands. * @param main_cb_cls a closure for "run", typically NULL. - * @param config_filename configuration filename. + * @param cfg configuration to use * @param exchanged exchange process handle: will be put in the * state as some commands - e.g. revoke - need to send * signal to it, for example to let it know to reload the @@ -761,7 +755,7 @@ main_wrapper_exchange_connect (void *cls) int TALER_TESTING_setup (TALER_TESTING_Main main_cb, void *main_cb_cls, - const char *config_filename, + const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_OS_Process *exchanged, int exchange_connect) { @@ -771,9 +765,6 @@ TALER_TESTING_setup (TALER_TESTING_Main main_cb, .main_cb_cls = main_cb_cls, /* needed to init the curl ctx */ .is = &is, - /* needed to read values like exchange port - * number to construct the exchange url.*/ - .config_filename = config_filename }; struct GNUNET_SIGNAL_Context *shc_chld; @@ -781,6 +772,7 @@ TALER_TESTING_setup (TALER_TESTING_Main main_cb, 0, sizeof (is)); is.exchanged = exchanged; + is.cfg = cfg; sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO); GNUNET_assert (NULL != sigpipe); -- cgit v1.2.3 From 9e0a813b38b791a0bc020b8f1ee16d2b5d8712c6 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 5 Feb 2020 21:11:57 +0100 Subject: implement auditor's exchange signing key caching (#6052) --- src/auditor/taler-auditor-httpd.c | 7 +- .../taler-auditor-httpd_deposit-confirmation.c | 131 +++++++++++++++------ .../taler-auditor-httpd_deposit-confirmation.h | 12 ++ 3 files changed, 113 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c index 4d5537e51..043d5b148 100644 --- a/src/auditor/taler-auditor-httpd.c +++ b/src/auditor/taler-auditor-httpd.c @@ -594,7 +594,7 @@ main (int argc, if (GNUNET_OK != auditor_serve_process_config ()) return 1; - + TEAH_DEPOSIT_CONFIRMATION_init (); /* check for systemd-style FD passing */ listen_pid = getenv ("LISTEN_PID"); listen_fds = getenv ("LISTEN_FDS"); @@ -635,7 +635,10 @@ main (int argc, fh = TALER_MHD_open_unix_path (serve_unixpath, unixpath_mode); if (-1 == fh) + { + TEAH_DEPOSIT_CONFIRMATION_done (); return 1; + } } mhd @@ -659,6 +662,7 @@ main (int argc, { fprintf (stderr, "Failed to start HTTP server.\n"); + TEAH_DEPOSIT_CONFIRMATION_done (); return 1; } @@ -732,6 +736,7 @@ main (int argc, break; } TALER_AUDITORDB_plugin_unload (TAH_plugin); + TEAH_DEPOSIT_CONFIRMATION_done (); return (GNUNET_SYSERR == ret) ? 1 : 0; } diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.c b/src/auditor/taler-auditor-httpd_deposit-confirmation.c index 7759b5538..87b1a26f7 100644 --- a/src/auditor/taler-auditor-httpd_deposit-confirmation.c +++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.c @@ -32,6 +32,19 @@ #include "taler-auditor-httpd_deposit-confirmation.h" +/** + * Cache of already verified exchange signing keys. Maps the hash of the + * `struct TALER_ExchangeSigningKeyValidityPS` to the (static) string + * "verified". Access to this map is guarded by the #lock. + */ +static struct GNUNET_CONTAINER_MultiHashMap *cache; + +/** + * Lock for operations on #cache. + */ +static pthread_mutex_t lock; + + /** * We have parsed the JSON information about the deposit, do some * basic sanity checks (especially that the signature on the coin is @@ -55,6 +68,8 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection, struct TALER_AUDITORDB_Session *session; enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Absolute now; + struct GNUNET_HashCode h; + int cached; now = GNUNET_TIME_absolute_get (); if ( (es->ep_start.abs_value_us > now.abs_value_us) || @@ -68,10 +83,6 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection, "master_sig (expired)"); } - /* TODO (#6052): consider having an in-memory cache of already - verified exchange signing keys, this could save us - a signature check AND a database transaction per - operation. */ /* check exchange signing key signature */ skv.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY); skv.purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)); @@ -80,40 +91,64 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection, skv.expire = GNUNET_TIME_absolute_hton (es->ep_expire); skv.end = GNUNET_TIME_absolute_hton (es->ep_end); skv.signkey_pub = es->exchange_pub; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, - &skv.purpose, - &es->master_sig.eddsa_signature, - &es->master_public_key.eddsa_pub)) - { - TALER_LOG_WARNING ("Invalid signature on exchange signing key\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID, - "master_sig"); - } - session = TAH_plugin->get_session (TAH_plugin->cls); - if (NULL == session) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_DB_SETUP_FAILED, - "failed to establish session with database"); - } - /* execute transaction */ - qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls, - session, - es); - if (0 > qs) + /* check our cache */ + GNUNET_CRYPTO_hash (&skv, + sizeof (skv), + &h); + GNUNET_assert (0 == pthread_mutex_lock (&lock)); + cached = GNUNET_CONTAINER_multihashmap_contains (cache, + &h); + GNUNET_assert (0 == pthread_mutex_unlock (&lock)); + + if (! cached) { - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - TALER_LOG_WARNING ("Failed to store exchange signing key in database\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR, - "failed to persist exchange signing key"); + /* Not in cache, need to verify the signature, persist it, and possibly cache it */ + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY, + &skv.purpose, + &es->master_sig.eddsa_signature, + &es->master_public_key.eddsa_pub)) + { + TALER_LOG_WARNING ("Invalid signature on exchange signing key\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_DEPOSIT_CONFIRMATION_SIGNATURE_INVALID, + "master_sig"); + } + + session = TAH_plugin->get_session (TAH_plugin->cls); + if (NULL == session) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_DB_SETUP_FAILED, + "failed to establish session with database"); + } + /* execute transaction */ + qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls, + session, + es); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + TALER_LOG_WARNING ("Failed to store exchange signing key in database\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_AUDITOR_EXCHANGE_STORE_DB_ERROR, + "failed to persist exchange signing key"); + } + + /* Cache it, due to concurreny it might already be in the cache, + so we do not cache it twice but also don't insist on the 'put' to + succeed. */ + GNUNET_assert (0 == pthread_mutex_lock (&lock)); + (void) GNUNET_CONTAINER_multihashmap_put (cache, + &h, + "verified", + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + GNUNET_assert (0 == pthread_mutex_unlock (&lock)); } /* check deposit confirmation signature */ @@ -237,4 +272,28 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, } +/** + * Initialize subsystem. + */ +void +TEAH_DEPOSIT_CONFIRMATION_init (void) +{ + cache = GNUNET_CONTAINER_multihashmap_create (32, + GNUNET_NO); + GNUNET_assert (0 == pthread_mutex_init (&lock, NULL)); +} + + +/** + * Shut down subsystem. + */ +void +TEAH_DEPOSIT_CONFIRMATION_done (void) +{ + GNUNET_CONTAINER_multihashmap_destroy (cache); + cache = NULL; + GNUNET_assert (0 == pthread_mutex_destroy (&lock)); +} + + /* end of taler-auditor-httpd_deposit-confirmation.c */ diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.h b/src/auditor/taler-auditor-httpd_deposit-confirmation.h index 842eb3562..531f3c93c 100644 --- a/src/auditor/taler-auditor-httpd_deposit-confirmation.h +++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.h @@ -25,6 +25,18 @@ #include #include "taler-auditor-httpd.h" +/** + * Initialize subsystem. + */ +void +TEAH_DEPOSIT_CONFIRMATION_init (void); + +/** + * Shut down subsystem. + */ +void +TEAH_DEPOSIT_CONFIRMATION_done (void); + /** * Handle a "/deposit-confirmation" request. Parses the JSON, and, if -- cgit v1.2.3 From 964cd7a459763a3ddc21f2b32f81a1dad5b7a4ce Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 8 Feb 2020 17:41:27 +0100 Subject: adding test for #6054 (duplicate WTID) --- src/auditor/test-auditor.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'src') diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh index 00ed05230..5f7d0c8c3 100755 --- a/src/auditor/test-auditor.sh +++ b/src/auditor/test-auditor.sh @@ -1473,6 +1473,49 @@ echo "UPDATE deposits SET wire='$OLD_WIRE' WHERE deposit_serial_id=${SERIAL}" | } +# Test for duplicate wire transfer subject +function test_27() { +echo "===========27: duplicate WTID detection =================" + +# Check wire transfer lag reported (no aggregator!) +# NOTE: This test is EXPECTED to fail for ~1h after +# re-generating the test database as we do not +# report lag of less than 1h (see GRACE_PERIOD in +# taler-wire-auditor.c) +if [ $DATABASE_AGE -gt 3600 ] +then + + pre_audit aggregator + + # Obtain data to duplicate. + ID=`echo "SELECT id FROM app_banktransaction WHERE debit_account_id=2 LIMIT 1" | psql $DB -Aqt` + WTID=`echo "SELECT subject FROM app_banktransaction WHERE debit_account_id=2 LIMIT 1" | psql $DB -Aqt` + echo "INSERT INTO app_banktransaction (amount,subject,date,credit_account_id,debit_account_id,cancelled) VALUES ('TESTKUDOS:1','$WTID',NOW(),12,2,'f')" | psql -Aqt $DB + + audit_only + post_audit + + echo -n "Testing inconsistency detection... " + + AMOUNT=`jq -r .wire_format_inconsistencies[0].amount < test-wire-audit.json` + if test "${AMOUNT}" != "TESTKUDOS:1" + then + exit_fail "Amount wrong, got ${AMOUNT}" + fi + + AMOUNT=`jq -r .total_wire_format_amount < test-wire-audit.json` + if test "${AMOUNT}" != "TESTKUDOS:1" + then + exit_fail "Wrong total wire format amount, got $AMOUNT" + fi + + # cannot easily undo aggregator, hence full reload + full_reload +else + echo "Test skipped (database too new)" +fi + +} # ************************************************** -- cgit v1.2.3 From db46491db994349a79cddae9fbfd6fd15ad16127 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 8 Feb 2020 17:49:56 +0100 Subject: fix uninitialized session --- src/auditor/taler-auditor-httpd_deposit-confirmation.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.c b/src/auditor/taler-auditor-httpd_deposit-confirmation.c index 87b1a26f7..ab233ebc3 100644 --- a/src/auditor/taler-auditor-httpd_deposit-confirmation.c +++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.c @@ -101,6 +101,15 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection, &h); GNUNET_assert (0 == pthread_mutex_unlock (&lock)); + session = TAH_plugin->get_session (TAH_plugin->cls); + if (NULL == session) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_DB_SETUP_FAILED, + "failed to establish session with database"); + } if (! cached) { /* Not in cache, need to verify the signature, persist it, and possibly cache it */ @@ -117,15 +126,6 @@ verify_and_execute_deposit_confirmation (struct MHD_Connection *connection, "master_sig"); } - session = TAH_plugin->get_session (TAH_plugin->cls); - if (NULL == session) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_DB_SETUP_FAILED, - "failed to establish session with database"); - } /* execute transaction */ qs = TAH_plugin->insert_exchange_signkey (TAH_plugin->cls, session, -- cgit v1.2.3 From 37c29157691715c3861b473b98444534fb33cbb2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 8 Feb 2020 17:55:22 +0100 Subject: do not report missing reserve closures of amount 0.0 --- src/auditor/taler-wire-auditor.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/auditor/taler-wire-auditor.c b/src/auditor/taler-wire-auditor.c index 5a68f165d..92664fb8f 100644 --- a/src/auditor/taler-wire-auditor.c +++ b/src/auditor/taler-wire-auditor.c @@ -704,13 +704,15 @@ check_pending_rc (void *cls, TALER_amount_add (&total_closure_amount_lag, &total_closure_amount_lag, &rc->amount)); - report (report_closure_lags, - json_pack ("{s:I, s:o, s:o, s:o, s:s}", - "row", (json_int_t) rc->rowid, - "amount", TALER_JSON_from_amount (&rc->amount), - "deadline", json_from_time_abs (rc->execution_date), - "wtid", GNUNET_JSON_from_data_auto (&rc->wtid), - "account", rc->receiver_account)); + if ( (0 != rc->amount.value) || + (0 != rc->amount.fraction) ) + report (report_closure_lags, + json_pack ("{s:I, s:o, s:o, s:o, s:s}", + "row", (json_int_t) rc->rowid, + "amount", TALER_JSON_from_amount (&rc->amount), + "deadline", json_from_time_abs (rc->execution_date), + "wtid", GNUNET_JSON_from_data_auto (&rc->wtid), + "account", rc->receiver_account)); pp.last_reserve_close_uuid = GNUNET_MIN (pp.last_reserve_close_uuid, rc->rowid); -- cgit v1.2.3 From f70596ff4c93a1906533fd488bf5fed1801eeb99 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 8 Feb 2020 18:38:15 +0100 Subject: adding UNIX socket and restart tests (#5309) --- src/exchange/Makefile.am | 4 +- src/exchange/test_taler_exchange_httpd_restart.sh | 117 ++++++++++++++++++ src/exchange/test_taler_exchange_unix.conf | 137 ++++++++++++++++++++++ 3 files changed, 257 insertions(+), 1 deletion(-) create mode 100755 src/exchange/test_taler_exchange_httpd_restart.sh create mode 100644 src/exchange/test_taler_exchange_unix.conf (limited to 'src') diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 50eb545b0..cf9f984de 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -83,7 +83,8 @@ taler_exchange_httpd_LDADD = \ AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; check_SCRIPTS = \ - test_taler_exchange_httpd.sh + test_taler_exchange_httpd.sh \ + test_taler_exchange_httpd_restart.sh if HAVE_EXPENSIVE_TESTS check_SCRIPTS += \ test_taler_exchange_httpd_afl.sh @@ -97,6 +98,7 @@ TESTS = \ EXTRA_DIST = \ test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv \ test_taler_exchange_httpd.conf \ + test_taler_exchange_unix.conf \ test_taler_exchange_httpd.get \ test_taler_exchange_httpd.post \ exchange.conf \ diff --git a/src/exchange/test_taler_exchange_httpd_restart.sh b/src/exchange/test_taler_exchange_httpd_restart.sh new file mode 100755 index 000000000..81ac04550 --- /dev/null +++ b/src/exchange/test_taler_exchange_httpd_restart.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# +# This file is part of TALER +# Copyright (C) 2020 Taler Systems SA +# +# TALER is free software; you can redistribute it and/or modify it under the +# terms of the GNU Affero General 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 General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License along with +# TALER; see the file COPYING. If not, If not, see +# +# +# This script launches an exchange (binding to a UNIX domain socket) and then +# restarts it in various ways (SIGHUP to re-read configuration, and SIGUSR1 to +# re-spawn a new binary). Basically, the goal is to make sure that the HTTP +# server survives these less common operations. +# +# +set -eu + +# Exit, with status code "skip" (no 'real' failure) +function exit_skip() { + echo $1 + exit 77 +} + +# Exit, with error message (hard failure) +function exit_fail() { + echo $1 + kill `jobs -p` >/dev/null 2>/dev/null || true + wait + exit 1 +} + +echo -n "Testing for curl" +curl --version >/dev/null /dev/null || exit 1 +# Run Exchange HTTPD (in background) +$PREFIX taler-exchange-httpd -c test_taler_exchange_unix.conf -i 2> test-exchange.log & + +# Where should we be bound to? +UNIXPATH=`taler-config -s exchange -f -o UNIXPATH` + +# Give HTTP time to start + +for n in `seq 1 100` +do + echo -n "." + sleep 0.1 + OK=1 + curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null && break + OK=0 +done +if [ 1 != $OK ] +then + echo "Failed to launch exchange" + kill -TERM $! + wait $! + echo Process status: $? + exit 77 +fi +echo " DONE" + +# Finally run test... +echo -n "Reloading keys ..." +kill -SIGUSR1 $! +sleep 1 +curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGUSR1 killed HTTP service" +echo " DONE" + +# Finally run test... +echo -n "Restarting program ..." +kill -SIGHUP $! +sleep 1 +curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGHUP killed HTTP service" +echo " DONE" + +echo -n "Waiting for parent to die ..." +wait $! +echo " DONE" + +echo -n "Testing child still alive ..." +curl --unix-socket "${UNIXPATH}" "http://ignored/" >/dev/null 2> /dev/null || exit_fail "SIGHUP killed HTTP service" +echo " DONE" + + +echo -n "Killing grandchild ..." +CPID=`ps x | grep taler-exchange-httpd | grep -v grep | awk '{print $1}'` +kill -TERM $CPID +while true +do + ps x | grep -v grep | grep taler-exchange-httpd > /dev/null || break +done +echo " DONE" + +# Return status code from exchange for this script +exit 0 diff --git a/src/exchange/test_taler_exchange_unix.conf b/src/exchange/test_taler_exchange_unix.conf new file mode 100644 index 000000000..d41df9abe --- /dev/null +++ b/src/exchange/test_taler_exchange_unix.conf @@ -0,0 +1,137 @@ +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_taler_exchange_httpd_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR +CURRENCY_ROUND_UNIT = EUR:0.01 + +[exchange] + +# Directory with our terms of service. +TERMS_DIR = ../../contrib/tos + +# Etag / filename for the terms of service. +TERMS_ETAG = 0 + + +# Directory with our privacy policy. +PRIVACY_DIR = ../../contrib/pp + +# Etag / filename for the privacy policy. +PRIVACY_ETAG = 0 + +# MAX_REQUESTS = 2 +# how long is one signkey valid? +SIGNKEY_DURATION = 4 weeks + +# how long are the signatures with the signkey valid? +LEGAL_DURATION = 2 years + +# how long do we generate denomination and signing keys +# ahead of time? +LOOKAHEAD_SIGN = 32 weeks 1 day + +# how long do we provide to clients denomination and signing keys +# ahead of time? +LOOKAHEAD_PROVIDE = 4 weeks 1 day + +# HTTP port the exchange listens to (we want to use UNIX domain sockets, +# so we use a port that just won't work on GNU/Linux without root rights) +PORT = 999 + +# Here we say we want to use a UNIX domain socket (to test that logic). +SERVE = unix + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + + +[exchangedb] +# After how long do we close idle reserves? The exchange +# and the auditor must agree on this value. We currently +# expect it to be globally defined for the whole system, +# as there is no way for wallets to query this value. Thus, +# it is only configurable for testing, and should be treated +# as constant in production. +IDLE_RESERVE_EXPIRATION_TIME = 4 weeks + + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[exchange-account-1] +PAYTO_URI = "payto://x-taler-bank/localhost:8082/3" +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +TALER_BANK_AUTH_METHOD = NONE + + +# Wire fees are specified by wire method +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2018, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + + +# Coins for the tests. +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 -- cgit v1.2.3 From 001f1552089fbd1e7fea540d2561c79bab2dfbac Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 8 Feb 2020 19:06:19 +0100 Subject: make script a bit nicer --- src/exchange/test_taler_exchange_httpd_restart.sh | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/exchange/test_taler_exchange_httpd_restart.sh b/src/exchange/test_taler_exchange_httpd_restart.sh index 81ac04550..f7d7a3050 100755 --- a/src/exchange/test_taler_exchange_httpd_restart.sh +++ b/src/exchange/test_taler_exchange_httpd_restart.sh @@ -49,7 +49,7 @@ unset XDG_CONFIG_HOME echo -n "Launching exchange ..." PREFIX= # Uncomment this line to run with valgrind... -# PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p" +# PREFIX="valgrind --trace-children=yes --leak-check=yes --track-fds=yes --error-exitcode=1 --log-file=valgrind.%p" # Setup keys. taler-exchange-keyup -c test_taler_exchange_unix.conf || exit 1 @@ -73,11 +73,7 @@ do done if [ 1 != $OK ] then - echo "Failed to launch exchange" - kill -TERM $! - wait $! - echo Process status: $? - exit 77 + exit_fail "Failed to launch exchange" fi echo " DONE" @@ -110,6 +106,7 @@ kill -TERM $CPID while true do ps x | grep -v grep | grep taler-exchange-httpd > /dev/null || break + sleep 0.1 done echo " DONE" -- cgit v1.2.3 From ae5583f04434cf39a5ae7a6f623bfc76322f9eb3 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 9 Feb 2020 15:53:28 +0100 Subject: avoid duping configuration, start to use PQ_connect_with_cfg --- src/auditordb/auditordb_plugin.c | 5 +---- src/auditordb/plugin_auditordb_postgres.c | 2 +- src/exchangedb/exchangedb_plugin.c | 6 ++---- src/exchangedb/plugin_exchangedb_postgres.c | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/auditordb/auditordb_plugin.c b/src/auditordb/auditordb_plugin.c index 19717fe87..d04e8815a 100644 --- a/src/auditordb/auditordb_plugin.c +++ b/src/auditordb/auditordb_plugin.c @@ -35,7 +35,6 @@ TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) { char *plugin_name; char *lib_name; - struct GNUNET_CONFIGURATION_Handle *cfg_dup; struct TALER_AUDITORDB_Plugin *plugin; if (GNUNET_SYSERR == @@ -53,14 +52,12 @@ TALER_AUDITORDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) "libtaler_plugin_auditordb_%s", plugin_name); GNUNET_free (plugin_name); - cfg_dup = GNUNET_CONFIGURATION_dup (cfg); plugin = GNUNET_PLUGIN_load (lib_name, - cfg_dup); + (void *) cfg); if (NULL != plugin) plugin->library_name = lib_name; else GNUNET_free (lib_name); - GNUNET_CONFIGURATION_destroy (cfg_dup); return plugin; } diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c index 30db303b0..08d24fd7f 100644 --- a/src/auditordb/plugin_auditordb_postgres.c +++ b/src/auditordb/plugin_auditordb_postgres.c @@ -3244,7 +3244,7 @@ postgres_get_predicted_balance (void *cls, void * libtaler_plugin_auditordb_postgres_init (void *cls) { - struct GNUNET_CONFIGURATION_Handle *cfg = cls; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct TALER_AUDITORDB_Plugin *plugin; const char *ec; diff --git a/src/exchangedb/exchangedb_plugin.c b/src/exchangedb/exchangedb_plugin.c index 8e61f860b..f4c2eea99 100644 --- a/src/exchangedb/exchangedb_plugin.c +++ b/src/exchangedb/exchangedb_plugin.c @@ -35,7 +35,6 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) { char *plugin_name; char *lib_name; - struct GNUNET_CONFIGURATION_Handle *cfg_dup; struct TALER_EXCHANGEDB_Plugin *plugin; if (GNUNET_SYSERR == @@ -53,13 +52,12 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) "libtaler_plugin_exchangedb_%s", plugin_name); GNUNET_free (plugin_name); - cfg_dup = GNUNET_CONFIGURATION_dup (cfg); - plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + plugin = GNUNET_PLUGIN_load (lib_name, + (void *) cfg); if (NULL != plugin) plugin->library_name = lib_name; else GNUNET_free (lib_name); - GNUNET_CONFIGURATION_destroy (cfg_dup); return plugin; } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 4d0b1bb6a..fe0021fa6 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -108,6 +108,11 @@ struct PostgresClosure */ pthread_key_t db_conn_threadlocal; + /** + * Our configuration. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + /** * Database connection string, as read from * the configuration. @@ -187,10 +192,11 @@ postgres_create_tables (void *cls) struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; - conn = GNUNET_PQ_connect (pc->connection_cfg_str, - pc->sql_dir, - NULL, - NULL); + conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "exchangedb-postgres", + "", + NULL, + NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); @@ -7212,12 +7218,13 @@ postgres_select_deposits_missing_wire (void *cls, void * libtaler_plugin_exchangedb_postgres_init (void *cls) { - struct GNUNET_CONFIGURATION_Handle *cfg = cls; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct TALER_EXCHANGEDB_Plugin *plugin; const char *ec; pg = GNUNET_new (struct PostgresClosure); + pg->cfg = cfg; pg->main_self = pthread_self (); /* loaded while single-threaded! */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, -- cgit v1.2.3 From cdaf1ce69b5fb56f09bbdc00942b03f039710614 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 9 Feb 2020 16:34:40 +0100 Subject: rename SQL files to make filenames consistent with versioning name --- src/auditordb/0000.sql | 293 ---------------------- src/auditordb/0001.sql | 239 ------------------ src/auditordb/Makefile.am | 4 +- src/auditordb/auditor-0000.sql | 293 ++++++++++++++++++++++ src/auditordb/auditor-0001.sql | 239 ++++++++++++++++++ src/auditordb/plugin_auditordb_postgres.c | 93 ++----- src/exchangedb/0000.sql | 293 ---------------------- src/exchangedb/0001.sql | 368 ---------------------------- src/exchangedb/Makefile.am | 4 +- src/exchangedb/exchange-0000.sql | 293 ++++++++++++++++++++++ src/exchangedb/exchange-0001.sql | 368 ++++++++++++++++++++++++++++ src/exchangedb/plugin_exchangedb_postgres.c | 68 ++--- 12 files changed, 1238 insertions(+), 1317 deletions(-) delete mode 100644 src/auditordb/0000.sql delete mode 100644 src/auditordb/0001.sql create mode 100644 src/auditordb/auditor-0000.sql create mode 100644 src/auditordb/auditor-0001.sql delete mode 100644 src/exchangedb/0000.sql delete mode 100644 src/exchangedb/0001.sql create mode 100644 src/exchangedb/exchange-0000.sql create mode 100644 src/exchangedb/exchange-0001.sql (limited to 'src') diff --git a/src/auditordb/0000.sql b/src/auditordb/0000.sql deleted file mode 100644 index 1483e2015..000000000 --- a/src/auditordb/0000.sql +++ /dev/null @@ -1,293 +0,0 @@ --- LICENSE AND COPYRIGHT --- --- Copyright (C) 2010 Hubert depesz Lubaczewski --- --- This program is distributed under the (Revised) BSD License: --- L --- --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions --- are met: --- --- * Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- --- * Redistributions in binary form must reproduce the above copyright --- notice, this list of conditions and the following disclaimer in the --- documentation and/or other materials provided with the distribution. --- --- * Neither the name of Hubert depesz Lubaczewski's Organization --- nor the names of its contributors may be used to endorse or --- promote products derived from this software without specific --- prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" --- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE --- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE --- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE --- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL --- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR --- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER --- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, --- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --- --- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql --- --- --- # NAME --- --- **Versioning** - simplistic take on tracking and applying changes to databases. --- --- # DESCRIPTION --- --- This project strives to provide simple way to manage changes to --- database. --- --- Instead of making changes on development server, then finding --- differences between production and development, deciding which ones --- should be installed on production, and finding a way to install them - --- you start with writing diffs themselves! --- --- # INSTALLATION --- --- To install versioning simply run install.versioning.sql in your database --- (all of them: production, stage, test, devel, ...). --- --- # USAGE --- --- In your files with patches to database, put whole logic in single --- transaction, and use \_v.\* functions - usually \_v.register_patch() at --- least to make sure everything is OK. --- --- For example. Let's assume you have patch files: --- --- ## 0001.sql: --- --- ``` --- create table users (id serial primary key, username text); --- ``` --- --- ## 0002.sql: --- --- ``` --- insert into users (username) values ('depesz'); --- ``` --- To change it to use versioning you would change the files, to this --- state: --- --- 0000.sql: --- --- ``` --- BEGIN; --- select _v.register_patch('000-base', NULL, NULL); --- create table users (id serial primary key, username text); --- COMMIT; --- ``` --- --- ## 0002.sql: --- --- ``` --- BEGIN; --- select _v.register_patch('001-users', ARRAY['000-base'], NULL); --- insert into users (username) values ('depesz'); --- COMMIT; --- ``` --- --- This will make sure that patch 001-users can only be applied after --- 000-base. --- --- # AVAILABLE FUNCTIONS --- --- ## \_v.register_patch( TEXT ) --- --- Registers named patch, or dies if it is already registered. --- --- Returns integer which is id of patch in \_v.patches table - only if it --- succeeded. --- --- ## \_v.register_patch( TEXT, TEXT[] ) --- --- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as --- array in second argument) are already registered. --- --- ## \_v.register_patch( TEXT, TEXT[], TEXT[] ) --- --- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches. --- --- Third argument is array of names of patches that conflict with current one. So --- if any of them is installed - register_patch will error out. --- --- ## \_v.unregister_patch( TEXT ) --- --- Removes information about given patch from the versioning data. --- --- It doesn't remove objects that were created by this patch - just removes --- metainformation. --- --- ## \_v.assert_user_is_superuser() --- --- Make sure that current patch is being loaded by superuser. --- --- If it's not - it will raise exception, and break transaction. --- --- ## \_v.assert_user_is_not_superuser() --- --- Make sure that current patch is not being loaded by superuser. --- --- If it is - it will raise exception, and break transaction. --- --- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... ) --- --- Make sure that current patch is being loaded by one of listed users. --- --- If ```current_user``` is not listed as one of arguments - function will raise --- exception and break the transaction. - -BEGIN; - --- This file adds versioning support to database it will be loaded to. --- It requires that PL/pgSQL is already loaded - will raise exception otherwise. --- All versioning "stuff" (tables, functions) is in "_v" schema. - --- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literaly nothing (0 rows). --- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. -CREATE SCHEMA IF NOT EXISTS _v; -COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; - -CREATE TABLE IF NOT EXISTS _v.patches ( - patch_name TEXT PRIMARY KEY, - applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(), - applied_by TEXT NOT NULL, - requires TEXT[], - conflicts TEXT[] -); -COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.'; -COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.'; -COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.'; -COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)'; -COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.'; -COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.'; - -CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$ -DECLARE - t_text TEXT; - t_text_a TEXT[]; - i INT4; -BEGIN - -- Thanks to this we know only one patch will be applied at a time - LOCK TABLE _v.patches IN EXCLUSIVE MODE; - - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; - IF FOUND THEN - RAISE EXCEPTION 'Patch % is already applied!', in_patch_name; - END IF; - - t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) ); - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' ); - END IF; - - IF array_upper( in_requirements, 1 ) IS NOT NULL THEN - t_text_a := '{}'; - FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i]; - IF NOT FOUND THEN - t_text_a := t_text_a || in_requirements[i]; - END IF; - END LOOP; - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' ); - END IF; - END IF; - - INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) ); - RETURN; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.'; - -CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$ - SELECT _v.register_patch( $1, $2, NULL ); -$$ language sql; -COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.'; -CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$ - SELECT _v.register_patch( $1, NULL, NULL ); -$$ language sql; -COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.'; - -CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$ -DECLARE - i INT4; - t_text_a TEXT[]; -BEGIN - -- Thanks to this we know only one patch will be applied at a time - LOCK TABLE _v.patches IN EXCLUSIVE MODE; - - t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) ); - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' ); - END IF; - - DELETE FROM _v.patches WHERE patch_name = in_patch_name; - GET DIAGNOSTICS i = ROW_COUNT; - IF i < 1 THEN - RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name; - END IF; - - RETURN; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.'; - -CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$ -DECLARE - t_text TEXT; -BEGIN - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; - IF NOT FOUND THEN - RAISE EXCEPTION 'Patch % is not applied!', in_patch_name; - END IF; - RETURN format('Patch %s is applied.', in_patch_name); -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$ -DECLARE - v_super bool; -BEGIN - SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; - IF v_super THEN - RETURN 'assert_user_is_superuser: OK'; - END IF; - RAISE EXCEPTION 'Current user is not superuser - cannot continue.'; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$ -DECLARE - v_super bool; -BEGIN - SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; - IF v_super THEN - RAISE EXCEPTION 'Current user is superuser - cannot continue.'; - END IF; - RETURN 'assert_user_is_not_superuser: OK'; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$ -DECLARE -BEGIN - IF current_user = any( p_acceptable_users ) THEN - RETURN 'assert_user_is_one_of: OK'; - END IF; - RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.'; - -COMMIT; diff --git a/src/auditordb/0001.sql b/src/auditordb/0001.sql deleted file mode 100644 index 3e666519c..000000000 --- a/src/auditordb/0001.sql +++ /dev/null @@ -1,239 +0,0 @@ --- --- This file is part of TALER --- Copyright (C) 2014--2020 Taler Systems SA --- --- TALER is free software; you can redistribute it and/or modify it under the --- terms of the GNU General 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 General Public License for more details. --- --- You should have received a copy of the GNU General Public License along with --- TALER; see the file COPYING. If not, see --- - --- Everything in one big transaction -BEGIN; - --- Check patch versioning is in place. -SELECT _v.register_patch('auditor-0001', NULL, NULL); - - -CREATE TABLE IF NOT EXISTS auditor_exchanges - (master_pub BYTEA PRIMARY KEY CHECK (LENGTH(master_pub)=32) - ,exchange_url VARCHAR NOT NULL - ); --- Table with list of signing keys of exchanges we are auditing -CREATE TABLE IF NOT EXISTS auditor_exchange_signkeys - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,ep_start INT8 NOT NULL - ,ep_expire INT8 NOT NULL - ,ep_end INT8 NOT NULL - ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32) - ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) - ); --- Table with all of the denomination keys that the auditor --- is aware of. -CREATE TABLE IF NOT EXISTS auditor_denominations - (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) - ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,valid_from INT8 NOT NULL - ,expire_withdraw INT8 NOT NULL - ,expire_deposit INT8 NOT NULL - ,expire_legal INT8 NOT NULL - ,coin_val INT8 NOT NULL - ,coin_frac INT4 NOT NULL - ,fee_withdraw_val INT8 NOT NULL - ,fee_withdraw_frac INT4 NOT NULL - ,fee_deposit_val INT8 NOT NULL - ,fee_deposit_frac INT4 NOT NULL - ,fee_refresh_val INT8 NOT NULL - ,fee_refresh_frac INT4 NOT NULL - ,fee_refund_val INT8 NOT NULL - ,fee_refund_frac INT4 NOT NULL - ); --- Table indicating up to which transactions the auditor has --- processed the exchange database. Used for SELECTing the --- statements to process. The indices below include the last --- serial ID from the respective tables that we have --- processed. Thus, we need to select those table entries that are --- strictly larger (and process in monotonically increasing --- order). -CREATE TABLE IF NOT EXISTS auditor_progress_reserve - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,last_reserve_in_serial_id INT8 NOT NULL DEFAULT 0 - ,last_reserve_out_serial_id INT8 NOT NULL DEFAULT 0 - ,last_reserve_recoup_serial_id INT8 NOT NULL DEFAULT 0 - ,last_reserve_close_serial_id INT8 NOT NULL DEFAULT 0 - ); -CREATE TABLE IF NOT EXISTS auditor_progress_aggregation - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,last_wire_out_serial_id INT8 NOT NULL DEFAULT 0 - ); -CREATE TABLE IF NOT EXISTS auditor_progress_deposit_confirmation - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,last_deposit_confirmation_serial_id INT8 NOT NULL DEFAULT 0 - ); -CREATE TABLE IF NOT EXISTS auditor_progress_coin - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,last_withdraw_serial_id INT8 NOT NULL DEFAULT 0 - ,last_deposit_serial_id INT8 NOT NULL DEFAULT 0 - ,last_melt_serial_id INT8 NOT NULL DEFAULT 0 - ,last_refund_serial_id INT8 NOT NULL DEFAULT 0 - ,last_recoup_serial_id INT8 NOT NULL DEFAULT 0 - ,last_recoup_refresh_serial_id INT8 NOT NULL DEFAULT 0 - ); -CREATE TABLE IF NOT EXISTS wire_auditor_account_progress - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,account_name TEXT NOT NULL - ,last_wire_reserve_in_serial_id INT8 NOT NULL DEFAULT 0 - ,last_wire_wire_out_serial_id INT8 NOT NULL DEFAULT 0 - ,wire_in_off INT8 - ,wire_out_off INT8 - ); -CREATE TABLE IF NOT EXISTS wire_auditor_progress - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,last_timestamp INT8 NOT NULL - ,last_reserve_close_uuid INT8 NOT NULL - ); --- Table with all of the customer reserves and their respective --- balances that the auditor is aware of. --- last_reserve_out_serial_id marks the last withdrawal from --- reserves_out about this reserve that the auditor is aware of, --- and last_reserve_in_serial_id is the last reserve_in --- operation about this reserve that the auditor is aware of. -CREATE TABLE IF NOT EXISTS auditor_reserves - (reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32) - ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,reserve_balance_val INT8 NOT NULL - ,reserve_balance_frac INT4 NOT NULL - ,withdraw_fee_balance_val INT8 NOT NULL - ,withdraw_fee_balance_frac INT4 NOT NULL - ,expiration_date INT8 NOT NULL - ,auditor_reserves_rowid BIGSERIAL UNIQUE - ,origin_account TEXT - ); -CREATE INDEX IF NOT EXISTS auditor_reserves_by_reserve_pub - ON auditor_reserves - (reserve_pub); --- Table with the sum of the balances of all customer reserves --- (by exchange's master public key) -CREATE TABLE IF NOT EXISTS auditor_reserve_balance - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,reserve_balance_val INT8 NOT NULL - ,reserve_balance_frac INT4 NOT NULL - ,withdraw_fee_balance_val INT8 NOT NULL - ,withdraw_fee_balance_frac INT4 NOT NULL - ); --- Table with the sum of the balances of all wire fees --- (by exchange's master public key) -CREATE TABLE IF NOT EXISTS auditor_wire_fee_balance - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,wire_fee_balance_val INT8 NOT NULL - ,wire_fee_balance_frac INT4 NOT NULL - ); --- Table with all of the outstanding denomination coins that the --- exchange is aware of and what the respective balances are --- (outstanding as well as issued overall which implies the --- maximum value at risk). We also count the number of coins --- issued (withdraw, refresh-reveal) and the number of coins seen --- at the exchange (refresh-commit, deposit), not just the amounts. */GNUNET_PQ_make_execute ( -CREATE TABLE IF NOT EXISTS auditor_denomination_pending - (denom_pub_hash BYTEA PRIMARY KEY REFERENCES auditor_denominations (denom_pub_hash) ON DELETE CASCADE - ,denom_balance_val INT8 NOT NULL - ,denom_balance_frac INT4 NOT NULL - ,denom_loss_val INT8 NOT NULL - ,denom_loss_frac INT4 NOT NULL - ,num_issued INT8 NOT NULL - ,denom_risk_val INT8 NOT NULL - ,denom_risk_frac INT4 NOT NULL - ,recoup_loss_val INT8 NOT NULL - ,recoup_loss_frac INT4 NOT NULL - ); --- Table with the sum of the outstanding coins from --- auditor_denomination_pending (denom_pubs must belong to the --- respective's exchange's master public key); it represents the --- auditor_balance_summary of the exchange at this point (modulo --- unexpected historic_loss-style events where denomination keys are --- compromised) -CREATE TABLE IF NOT EXISTS auditor_balance_summary - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,denom_balance_val INT8 NOT NULL - ,denom_balance_frac INT4 NOT NULL - ,deposit_fee_balance_val INT8 NOT NULL - ,deposit_fee_balance_frac INT4 NOT NULL - ,melt_fee_balance_val INT8 NOT NULL - ,melt_fee_balance_frac INT4 NOT NULL - ,refund_fee_balance_val INT8 NOT NULL - ,refund_fee_balance_frac INT4 NOT NULL - ,risk_val INT8 NOT NULL - ,risk_frac INT4 NOT NULL - ,loss_val INT8 NOT NULL - ,loss_frac INT4 NOT NULL - ,irregular_recoup_val INT8 NOT NULL - ,irregular_recoup_frac INT4 NOT NULL - ); --- Table with historic profits; basically, when a denom_pub has --- expired and everything associated with it is garbage collected, --- the final profits end up in here; note that the denom_pub here --- is not a foreign key, we just keep it as a reference point. --- revenue_balance is the sum of all of the profits we made on the --- coin except for withdraw fees (which are in --- historic_reserve_revenue); the deposit, melt and refund fees are given --- individually; the delta to the revenue_balance is from coins that --- were withdrawn but never deposited prior to expiration. -CREATE TABLE IF NOT EXISTS auditor_historic_denomination_revenue - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) - ,revenue_timestamp INT8 NOT NULL - ,revenue_balance_val INT8 NOT NULL - ,revenue_balance_frac INT4 NOT NULL - ,loss_balance_val INT8 NOT NULL - ,loss_balance_frac INT4 NOT NULL - ); --- Table with historic profits from reserves; we eventually --- GC auditor_historic_reserve_revenue, and then store the totals --- in here (by time intervals). -CREATE TABLE IF NOT EXISTS auditor_historic_reserve_summary - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,start_date INT8 NOT NULL - ,end_date INT8 NOT NULL - ,reserve_profits_val INT8 NOT NULL - ,reserve_profits_frac INT4 NOT NULL - ); -CREATE INDEX IF NOT EXISTS auditor_historic_reserve_summary_by_master_pub_start_date - ON auditor_historic_reserve_summary - (master_pub - ,start_date); --- Table with deposit confirmation sent to us by merchants; --- we must check that the exchange reported these properly. -CREATE TABLE IF NOT EXISTS deposit_confirmations - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,serial_id BIGSERIAL UNIQUE - ,h_contract_terms BYTEA CHECK (LENGTH(h_contract_terms)=64) - ,h_wire BYTEA CHECK (LENGTH(h_wire)=64) - ,timestamp INT8 NOT NULL - ,refund_deadline INT8 NOT NULL - ,amount_without_fee_val INT8 NOT NULL - ,amount_without_fee_frac INT4 NOT NULL - ,coin_pub BYTEA CHECK (LENGTH(coin_pub)=32) - ,merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32) - ,exchange_sig BYTEA CHECK (LENGTH(exchange_sig)=64) - ,exchange_pub BYTEA CHECK (LENGTH(exchange_pub)=32) - ,master_sig BYTEA CHECK (LENGTH(master_sig)=64) - ,PRIMARY KEY (h_contract_terms,h_wire,coin_pub,merchant_pub,exchange_sig,exchange_pub,master_sig) - ); --- Table with the sum of the ledger, auditor_historic_revenue and --- the auditor_reserve_balance. This is the --- final amount that the exchange should have in its bank account --- right now. -CREATE TABLE IF NOT EXISTS auditor_predicted_result - (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE - ,balance_val INT8 NOT NULL - ,balance_frac INT4 NOT NULL - ); - --- Finally, commit everything -COMMIT; diff --git a/src/auditordb/Makefile.am b/src/auditordb/Makefile.am index 1378c5496..3efdc200e 100644 --- a/src/auditordb/Makefile.am +++ b/src/auditordb/Makefile.am @@ -14,8 +14,8 @@ pkgcfg_DATA = \ sqldir = $(prefix)/share/taler/sql/auditor/ sql_DATA = \ - 0000.sql \ - 0001.sql \ + auditor-0000.sql \ + auditor-0001.sql \ drop0000.sql \ restart0000.sql diff --git a/src/auditordb/auditor-0000.sql b/src/auditordb/auditor-0000.sql new file mode 100644 index 000000000..1483e2015 --- /dev/null +++ b/src/auditordb/auditor-0000.sql @@ -0,0 +1,293 @@ +-- LICENSE AND COPYRIGHT +-- +-- Copyright (C) 2010 Hubert depesz Lubaczewski +-- +-- This program is distributed under the (Revised) BSD License: +-- L +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- +-- * Neither the name of Hubert depesz Lubaczewski's Organization +-- nor the names of its contributors may be used to endorse or +-- promote products derived from this software without specific +-- prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-- +-- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql +-- +-- +-- # NAME +-- +-- **Versioning** - simplistic take on tracking and applying changes to databases. +-- +-- # DESCRIPTION +-- +-- This project strives to provide simple way to manage changes to +-- database. +-- +-- Instead of making changes on development server, then finding +-- differences between production and development, deciding which ones +-- should be installed on production, and finding a way to install them - +-- you start with writing diffs themselves! +-- +-- # INSTALLATION +-- +-- To install versioning simply run install.versioning.sql in your database +-- (all of them: production, stage, test, devel, ...). +-- +-- # USAGE +-- +-- In your files with patches to database, put whole logic in single +-- transaction, and use \_v.\* functions - usually \_v.register_patch() at +-- least to make sure everything is OK. +-- +-- For example. Let's assume you have patch files: +-- +-- ## 0001.sql: +-- +-- ``` +-- create table users (id serial primary key, username text); +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- insert into users (username) values ('depesz'); +-- ``` +-- To change it to use versioning you would change the files, to this +-- state: +-- +-- 0000.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('000-base', NULL, NULL); +-- create table users (id serial primary key, username text); +-- COMMIT; +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('001-users', ARRAY['000-base'], NULL); +-- insert into users (username) values ('depesz'); +-- COMMIT; +-- ``` +-- +-- This will make sure that patch 001-users can only be applied after +-- 000-base. +-- +-- # AVAILABLE FUNCTIONS +-- +-- ## \_v.register_patch( TEXT ) +-- +-- Registers named patch, or dies if it is already registered. +-- +-- Returns integer which is id of patch in \_v.patches table - only if it +-- succeeded. +-- +-- ## \_v.register_patch( TEXT, TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as +-- array in second argument) are already registered. +-- +-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches. +-- +-- Third argument is array of names of patches that conflict with current one. So +-- if any of them is installed - register_patch will error out. +-- +-- ## \_v.unregister_patch( TEXT ) +-- +-- Removes information about given patch from the versioning data. +-- +-- It doesn't remove objects that were created by this patch - just removes +-- metainformation. +-- +-- ## \_v.assert_user_is_superuser() +-- +-- Make sure that current patch is being loaded by superuser. +-- +-- If it's not - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_not_superuser() +-- +-- Make sure that current patch is not being loaded by superuser. +-- +-- If it is - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... ) +-- +-- Make sure that current patch is being loaded by one of listed users. +-- +-- If ```current_user``` is not listed as one of arguments - function will raise +-- exception and break the transaction. + +BEGIN; + +-- This file adds versioning support to database it will be loaded to. +-- It requires that PL/pgSQL is already loaded - will raise exception otherwise. +-- All versioning "stuff" (tables, functions) is in "_v" schema. + +-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literaly nothing (0 rows). +-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. +CREATE SCHEMA IF NOT EXISTS _v; +COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; + +CREATE TABLE IF NOT EXISTS _v.patches ( + patch_name TEXT PRIMARY KEY, + applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(), + applied_by TEXT NOT NULL, + requires TEXT[], + conflicts TEXT[] +); +COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.'; +COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.'; +COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.'; +COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)'; +COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.'; +COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + t_text TEXT; + t_text_a TEXT[]; + i INT4; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF FOUND THEN + RAISE EXCEPTION 'Patch % is already applied!', in_patch_name; + END IF; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' ); + END IF; + + IF array_upper( in_requirements, 1 ) IS NOT NULL THEN + t_text_a := '{}'; + FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i]; + IF NOT FOUND THEN + t_text_a := t_text_a || in_requirements[i]; + END IF; + END LOOP; + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' ); + END IF; + END IF; + + INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) ); + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, $2, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.'; +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, NULL, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.'; + +CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + i INT4; + t_text_a TEXT[]; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' ); + END IF; + + DELETE FROM _v.patches WHERE patch_name = in_patch_name; + GET DIAGNOSTICS i = ROW_COUNT; + IF i < 1 THEN + RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name; + END IF; + + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.'; + +CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$ +DECLARE + t_text TEXT; +BEGIN + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF NOT FOUND THEN + RAISE EXCEPTION 'Patch % is not applied!', in_patch_name; + END IF; + RETURN format('Patch %s is applied.', in_patch_name); +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RETURN 'assert_user_is_superuser: OK'; + END IF; + RAISE EXCEPTION 'Current user is not superuser - cannot continue.'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RAISE EXCEPTION 'Current user is superuser - cannot continue.'; + END IF; + RETURN 'assert_user_is_not_superuser: OK'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$ +DECLARE +BEGIN + IF current_user = any( p_acceptable_users ) THEN + RETURN 'assert_user_is_one_of: OK'; + END IF; + RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.'; + +COMMIT; diff --git a/src/auditordb/auditor-0001.sql b/src/auditordb/auditor-0001.sql new file mode 100644 index 000000000..3e666519c --- /dev/null +++ b/src/auditordb/auditor-0001.sql @@ -0,0 +1,239 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2020 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General 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 General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('auditor-0001', NULL, NULL); + + +CREATE TABLE IF NOT EXISTS auditor_exchanges + (master_pub BYTEA PRIMARY KEY CHECK (LENGTH(master_pub)=32) + ,exchange_url VARCHAR NOT NULL + ); +-- Table with list of signing keys of exchanges we are auditing +CREATE TABLE IF NOT EXISTS auditor_exchange_signkeys + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,ep_start INT8 NOT NULL + ,ep_expire INT8 NOT NULL + ,ep_end INT8 NOT NULL + ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32) + ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) + ); +-- Table with all of the denomination keys that the auditor +-- is aware of. +CREATE TABLE IF NOT EXISTS auditor_denominations + (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) + ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,valid_from INT8 NOT NULL + ,expire_withdraw INT8 NOT NULL + ,expire_deposit INT8 NOT NULL + ,expire_legal INT8 NOT NULL + ,coin_val INT8 NOT NULL + ,coin_frac INT4 NOT NULL + ,fee_withdraw_val INT8 NOT NULL + ,fee_withdraw_frac INT4 NOT NULL + ,fee_deposit_val INT8 NOT NULL + ,fee_deposit_frac INT4 NOT NULL + ,fee_refresh_val INT8 NOT NULL + ,fee_refresh_frac INT4 NOT NULL + ,fee_refund_val INT8 NOT NULL + ,fee_refund_frac INT4 NOT NULL + ); +-- Table indicating up to which transactions the auditor has +-- processed the exchange database. Used for SELECTing the +-- statements to process. The indices below include the last +-- serial ID from the respective tables that we have +-- processed. Thus, we need to select those table entries that are +-- strictly larger (and process in monotonically increasing +-- order). +CREATE TABLE IF NOT EXISTS auditor_progress_reserve + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,last_reserve_in_serial_id INT8 NOT NULL DEFAULT 0 + ,last_reserve_out_serial_id INT8 NOT NULL DEFAULT 0 + ,last_reserve_recoup_serial_id INT8 NOT NULL DEFAULT 0 + ,last_reserve_close_serial_id INT8 NOT NULL DEFAULT 0 + ); +CREATE TABLE IF NOT EXISTS auditor_progress_aggregation + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,last_wire_out_serial_id INT8 NOT NULL DEFAULT 0 + ); +CREATE TABLE IF NOT EXISTS auditor_progress_deposit_confirmation + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,last_deposit_confirmation_serial_id INT8 NOT NULL DEFAULT 0 + ); +CREATE TABLE IF NOT EXISTS auditor_progress_coin + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,last_withdraw_serial_id INT8 NOT NULL DEFAULT 0 + ,last_deposit_serial_id INT8 NOT NULL DEFAULT 0 + ,last_melt_serial_id INT8 NOT NULL DEFAULT 0 + ,last_refund_serial_id INT8 NOT NULL DEFAULT 0 + ,last_recoup_serial_id INT8 NOT NULL DEFAULT 0 + ,last_recoup_refresh_serial_id INT8 NOT NULL DEFAULT 0 + ); +CREATE TABLE IF NOT EXISTS wire_auditor_account_progress + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,account_name TEXT NOT NULL + ,last_wire_reserve_in_serial_id INT8 NOT NULL DEFAULT 0 + ,last_wire_wire_out_serial_id INT8 NOT NULL DEFAULT 0 + ,wire_in_off INT8 + ,wire_out_off INT8 + ); +CREATE TABLE IF NOT EXISTS wire_auditor_progress + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,last_timestamp INT8 NOT NULL + ,last_reserve_close_uuid INT8 NOT NULL + ); +-- Table with all of the customer reserves and their respective +-- balances that the auditor is aware of. +-- last_reserve_out_serial_id marks the last withdrawal from +-- reserves_out about this reserve that the auditor is aware of, +-- and last_reserve_in_serial_id is the last reserve_in +-- operation about this reserve that the auditor is aware of. +CREATE TABLE IF NOT EXISTS auditor_reserves + (reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32) + ,master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,reserve_balance_val INT8 NOT NULL + ,reserve_balance_frac INT4 NOT NULL + ,withdraw_fee_balance_val INT8 NOT NULL + ,withdraw_fee_balance_frac INT4 NOT NULL + ,expiration_date INT8 NOT NULL + ,auditor_reserves_rowid BIGSERIAL UNIQUE + ,origin_account TEXT + ); +CREATE INDEX IF NOT EXISTS auditor_reserves_by_reserve_pub + ON auditor_reserves + (reserve_pub); +-- Table with the sum of the balances of all customer reserves +-- (by exchange's master public key) +CREATE TABLE IF NOT EXISTS auditor_reserve_balance + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,reserve_balance_val INT8 NOT NULL + ,reserve_balance_frac INT4 NOT NULL + ,withdraw_fee_balance_val INT8 NOT NULL + ,withdraw_fee_balance_frac INT4 NOT NULL + ); +-- Table with the sum of the balances of all wire fees +-- (by exchange's master public key) +CREATE TABLE IF NOT EXISTS auditor_wire_fee_balance + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,wire_fee_balance_val INT8 NOT NULL + ,wire_fee_balance_frac INT4 NOT NULL + ); +-- Table with all of the outstanding denomination coins that the +-- exchange is aware of and what the respective balances are +-- (outstanding as well as issued overall which implies the +-- maximum value at risk). We also count the number of coins +-- issued (withdraw, refresh-reveal) and the number of coins seen +-- at the exchange (refresh-commit, deposit), not just the amounts. */GNUNET_PQ_make_execute ( +CREATE TABLE IF NOT EXISTS auditor_denomination_pending + (denom_pub_hash BYTEA PRIMARY KEY REFERENCES auditor_denominations (denom_pub_hash) ON DELETE CASCADE + ,denom_balance_val INT8 NOT NULL + ,denom_balance_frac INT4 NOT NULL + ,denom_loss_val INT8 NOT NULL + ,denom_loss_frac INT4 NOT NULL + ,num_issued INT8 NOT NULL + ,denom_risk_val INT8 NOT NULL + ,denom_risk_frac INT4 NOT NULL + ,recoup_loss_val INT8 NOT NULL + ,recoup_loss_frac INT4 NOT NULL + ); +-- Table with the sum of the outstanding coins from +-- auditor_denomination_pending (denom_pubs must belong to the +-- respective's exchange's master public key); it represents the +-- auditor_balance_summary of the exchange at this point (modulo +-- unexpected historic_loss-style events where denomination keys are +-- compromised) +CREATE TABLE IF NOT EXISTS auditor_balance_summary + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,denom_balance_val INT8 NOT NULL + ,denom_balance_frac INT4 NOT NULL + ,deposit_fee_balance_val INT8 NOT NULL + ,deposit_fee_balance_frac INT4 NOT NULL + ,melt_fee_balance_val INT8 NOT NULL + ,melt_fee_balance_frac INT4 NOT NULL + ,refund_fee_balance_val INT8 NOT NULL + ,refund_fee_balance_frac INT4 NOT NULL + ,risk_val INT8 NOT NULL + ,risk_frac INT4 NOT NULL + ,loss_val INT8 NOT NULL + ,loss_frac INT4 NOT NULL + ,irregular_recoup_val INT8 NOT NULL + ,irregular_recoup_frac INT4 NOT NULL + ); +-- Table with historic profits; basically, when a denom_pub has +-- expired and everything associated with it is garbage collected, +-- the final profits end up in here; note that the denom_pub here +-- is not a foreign key, we just keep it as a reference point. +-- revenue_balance is the sum of all of the profits we made on the +-- coin except for withdraw fees (which are in +-- historic_reserve_revenue); the deposit, melt and refund fees are given +-- individually; the delta to the revenue_balance is from coins that +-- were withdrawn but never deposited prior to expiration. +CREATE TABLE IF NOT EXISTS auditor_historic_denomination_revenue + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) + ,revenue_timestamp INT8 NOT NULL + ,revenue_balance_val INT8 NOT NULL + ,revenue_balance_frac INT4 NOT NULL + ,loss_balance_val INT8 NOT NULL + ,loss_balance_frac INT4 NOT NULL + ); +-- Table with historic profits from reserves; we eventually +-- GC auditor_historic_reserve_revenue, and then store the totals +-- in here (by time intervals). +CREATE TABLE IF NOT EXISTS auditor_historic_reserve_summary + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,start_date INT8 NOT NULL + ,end_date INT8 NOT NULL + ,reserve_profits_val INT8 NOT NULL + ,reserve_profits_frac INT4 NOT NULL + ); +CREATE INDEX IF NOT EXISTS auditor_historic_reserve_summary_by_master_pub_start_date + ON auditor_historic_reserve_summary + (master_pub + ,start_date); +-- Table with deposit confirmation sent to us by merchants; +-- we must check that the exchange reported these properly. +CREATE TABLE IF NOT EXISTS deposit_confirmations + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,serial_id BIGSERIAL UNIQUE + ,h_contract_terms BYTEA CHECK (LENGTH(h_contract_terms)=64) + ,h_wire BYTEA CHECK (LENGTH(h_wire)=64) + ,timestamp INT8 NOT NULL + ,refund_deadline INT8 NOT NULL + ,amount_without_fee_val INT8 NOT NULL + ,amount_without_fee_frac INT4 NOT NULL + ,coin_pub BYTEA CHECK (LENGTH(coin_pub)=32) + ,merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32) + ,exchange_sig BYTEA CHECK (LENGTH(exchange_sig)=64) + ,exchange_pub BYTEA CHECK (LENGTH(exchange_pub)=32) + ,master_sig BYTEA CHECK (LENGTH(master_sig)=64) + ,PRIMARY KEY (h_contract_terms,h_wire,coin_pub,merchant_pub,exchange_sig,exchange_pub,master_sig) + ); +-- Table with the sum of the ledger, auditor_historic_revenue and +-- the auditor_reserve_balance. This is the +-- final amount that the exchange should have in its bank account +-- right now. +CREATE TABLE IF NOT EXISTS auditor_predicted_result + (master_pub BYTEA CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE + ,balance_val INT8 NOT NULL + ,balance_frac INT4 NOT NULL + ); + +-- Finally, commit everything +COMMIT; diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c index 08d24fd7f..ca07d406b 100644 --- a/src/auditordb/plugin_auditordb_postgres.c +++ b/src/auditordb/plugin_auditordb_postgres.c @@ -80,15 +80,9 @@ struct PostgresClosure pthread_key_t db_conn_threadlocal; /** - * Directory with SQL statements to run to create tables. + * Our configuration. */ - char *sql_dir; - - /** - * Database connection string, as read from - * the configuration. - */ - char *connection_cfg_str; + const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Which currency should we assume all amounts to be in? @@ -114,16 +108,12 @@ postgres_drop_tables (void *cls, { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; - char *exec_dir; - - GNUNET_asprintf (&exec_dir, - (drop_exchangelist) ? "%sdrop" : "%srestart", - pc->sql_dir); - conn = GNUNET_PQ_connect (pc->connection_cfg_str, - exec_dir, - NULL, - NULL); - GNUNET_free (exec_dir); + + conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "auditordb-postgres", + (drop_exchangelist) ? "drop" : "restart", + NULL, + NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); @@ -143,10 +133,11 @@ postgres_create_tables (void *cls) struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; - conn = GNUNET_PQ_connect (pc->connection_cfg_str, - pc->sql_dir, - NULL, - NULL); + conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "auditordb-postgres", + "auditor-", + NULL, + NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); @@ -734,10 +725,11 @@ postgres_get_session (void *cls) GNUNET_PQ_reconnect_if_down (session->conn); return session; } - db_conn = GNUNET_PQ_connect (pc->connection_cfg_str, - NULL, - NULL, - ps); + db_conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "auditordb-postgres", + NULL, + NULL, + ps); if (NULL == db_conn) return NULL; session = GNUNET_new (struct TALER_AUDITORDB_Session); @@ -897,10 +889,11 @@ postgres_gc (void *cls) }; now = GNUNET_TIME_absolute_get (); - conn = GNUNET_PQ_connect (pc->connection_cfg_str, - NULL, - NULL, - ps); + conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "auditordb-postgres", + NULL, + NULL, + ps); if (NULL == conn) return GNUNET_SYSERR; GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -3247,50 +3240,16 @@ libtaler_plugin_auditordb_postgres_init (void *cls) const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct TALER_AUDITORDB_Plugin *plugin; - const char *ec; pg = GNUNET_new (struct PostgresClosure); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - "auditordb-postgres", - "SQL_DIR", - &pg->sql_dir)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "auditordb-postgres", - "SQL_DIR"); - GNUNET_free (pg); - return NULL; - } + pg->cfg = cfg; if (0 != pthread_key_create (&pg->db_conn_threadlocal, &db_conn_destroy)) { TALER_LOG_ERROR ("Cannnot create pthread key.\n"); - GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } - ec = getenv ("TALER_AUDITORDB_POSTGRES_CONFIG"); - if (NULL != ec) - { - pg->connection_cfg_str = GNUNET_strdup (ec); - } - else - { - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "auditordb-postgres", - "CONFIG", - &pg->connection_cfg_str)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "auditordb-postgres", - "CONFIG"); - GNUNET_free (pg->sql_dir); - GNUNET_free (pg); - return NULL; - } - } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", @@ -3300,8 +3259,6 @@ libtaler_plugin_auditordb_postgres_init (void *cls) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); - GNUNET_free (pg->connection_cfg_str); - GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; } @@ -3407,8 +3364,6 @@ libtaler_plugin_auditordb_postgres_done (void *cls) struct TALER_AUDITORDB_Plugin *plugin = cls; struct PostgresClosure *pg = plugin->cls; - GNUNET_free (pg->connection_cfg_str); - GNUNET_free (pg->sql_dir); GNUNET_free (pg->currency); GNUNET_free (pg); GNUNET_free (plugin); diff --git a/src/exchangedb/0000.sql b/src/exchangedb/0000.sql deleted file mode 100644 index 1483e2015..000000000 --- a/src/exchangedb/0000.sql +++ /dev/null @@ -1,293 +0,0 @@ --- LICENSE AND COPYRIGHT --- --- Copyright (C) 2010 Hubert depesz Lubaczewski --- --- This program is distributed under the (Revised) BSD License: --- L --- --- Redistribution and use in source and binary forms, with or without --- modification, are permitted provided that the following conditions --- are met: --- --- * Redistributions of source code must retain the above copyright --- notice, this list of conditions and the following disclaimer. --- --- * Redistributions in binary form must reproduce the above copyright --- notice, this list of conditions and the following disclaimer in the --- documentation and/or other materials provided with the distribution. --- --- * Neither the name of Hubert depesz Lubaczewski's Organization --- nor the names of its contributors may be used to endorse or --- promote products derived from this software without specific --- prior written permission. --- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" --- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE --- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE --- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE --- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL --- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR --- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER --- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, --- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --- --- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql --- --- --- # NAME --- --- **Versioning** - simplistic take on tracking and applying changes to databases. --- --- # DESCRIPTION --- --- This project strives to provide simple way to manage changes to --- database. --- --- Instead of making changes on development server, then finding --- differences between production and development, deciding which ones --- should be installed on production, and finding a way to install them - --- you start with writing diffs themselves! --- --- # INSTALLATION --- --- To install versioning simply run install.versioning.sql in your database --- (all of them: production, stage, test, devel, ...). --- --- # USAGE --- --- In your files with patches to database, put whole logic in single --- transaction, and use \_v.\* functions - usually \_v.register_patch() at --- least to make sure everything is OK. --- --- For example. Let's assume you have patch files: --- --- ## 0001.sql: --- --- ``` --- create table users (id serial primary key, username text); --- ``` --- --- ## 0002.sql: --- --- ``` --- insert into users (username) values ('depesz'); --- ``` --- To change it to use versioning you would change the files, to this --- state: --- --- 0000.sql: --- --- ``` --- BEGIN; --- select _v.register_patch('000-base', NULL, NULL); --- create table users (id serial primary key, username text); --- COMMIT; --- ``` --- --- ## 0002.sql: --- --- ``` --- BEGIN; --- select _v.register_patch('001-users', ARRAY['000-base'], NULL); --- insert into users (username) values ('depesz'); --- COMMIT; --- ``` --- --- This will make sure that patch 001-users can only be applied after --- 000-base. --- --- # AVAILABLE FUNCTIONS --- --- ## \_v.register_patch( TEXT ) --- --- Registers named patch, or dies if it is already registered. --- --- Returns integer which is id of patch in \_v.patches table - only if it --- succeeded. --- --- ## \_v.register_patch( TEXT, TEXT[] ) --- --- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as --- array in second argument) are already registered. --- --- ## \_v.register_patch( TEXT, TEXT[], TEXT[] ) --- --- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches. --- --- Third argument is array of names of patches that conflict with current one. So --- if any of them is installed - register_patch will error out. --- --- ## \_v.unregister_patch( TEXT ) --- --- Removes information about given patch from the versioning data. --- --- It doesn't remove objects that were created by this patch - just removes --- metainformation. --- --- ## \_v.assert_user_is_superuser() --- --- Make sure that current patch is being loaded by superuser. --- --- If it's not - it will raise exception, and break transaction. --- --- ## \_v.assert_user_is_not_superuser() --- --- Make sure that current patch is not being loaded by superuser. --- --- If it is - it will raise exception, and break transaction. --- --- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... ) --- --- Make sure that current patch is being loaded by one of listed users. --- --- If ```current_user``` is not listed as one of arguments - function will raise --- exception and break the transaction. - -BEGIN; - --- This file adds versioning support to database it will be loaded to. --- It requires that PL/pgSQL is already loaded - will raise exception otherwise. --- All versioning "stuff" (tables, functions) is in "_v" schema. - --- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literaly nothing (0 rows). --- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. -CREATE SCHEMA IF NOT EXISTS _v; -COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; - -CREATE TABLE IF NOT EXISTS _v.patches ( - patch_name TEXT PRIMARY KEY, - applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(), - applied_by TEXT NOT NULL, - requires TEXT[], - conflicts TEXT[] -); -COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.'; -COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.'; -COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.'; -COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)'; -COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.'; -COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.'; - -CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$ -DECLARE - t_text TEXT; - t_text_a TEXT[]; - i INT4; -BEGIN - -- Thanks to this we know only one patch will be applied at a time - LOCK TABLE _v.patches IN EXCLUSIVE MODE; - - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; - IF FOUND THEN - RAISE EXCEPTION 'Patch % is already applied!', in_patch_name; - END IF; - - t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) ); - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' ); - END IF; - - IF array_upper( in_requirements, 1 ) IS NOT NULL THEN - t_text_a := '{}'; - FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i]; - IF NOT FOUND THEN - t_text_a := t_text_a || in_requirements[i]; - END IF; - END LOOP; - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' ); - END IF; - END IF; - - INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) ); - RETURN; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.'; - -CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$ - SELECT _v.register_patch( $1, $2, NULL ); -$$ language sql; -COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.'; -CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$ - SELECT _v.register_patch( $1, NULL, NULL ); -$$ language sql; -COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.'; - -CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$ -DECLARE - i INT4; - t_text_a TEXT[]; -BEGIN - -- Thanks to this we know only one patch will be applied at a time - LOCK TABLE _v.patches IN EXCLUSIVE MODE; - - t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) ); - IF array_upper( t_text_a, 1 ) IS NOT NULL THEN - RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' ); - END IF; - - DELETE FROM _v.patches WHERE patch_name = in_patch_name; - GET DIAGNOSTICS i = ROW_COUNT; - IF i < 1 THEN - RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name; - END IF; - - RETURN; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.'; - -CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$ -DECLARE - t_text TEXT; -BEGIN - SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; - IF NOT FOUND THEN - RAISE EXCEPTION 'Patch % is not applied!', in_patch_name; - END IF; - RETURN format('Patch %s is applied.', in_patch_name); -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$ -DECLARE - v_super bool; -BEGIN - SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; - IF v_super THEN - RETURN 'assert_user_is_superuser: OK'; - END IF; - RAISE EXCEPTION 'Current user is not superuser - cannot continue.'; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$ -DECLARE - v_super bool; -BEGIN - SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; - IF v_super THEN - RAISE EXCEPTION 'Current user is superuser - cannot continue.'; - END IF; - RETURN 'assert_user_is_not_superuser: OK'; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.'; - -CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$ -DECLARE -BEGIN - IF current_user = any( p_acceptable_users ) THEN - RETURN 'assert_user_is_one_of: OK'; - END IF; - RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users; -END; -$$ language plpgsql; -COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.'; - -COMMIT; diff --git a/src/exchangedb/0001.sql b/src/exchangedb/0001.sql deleted file mode 100644 index 02dc68cf4..000000000 --- a/src/exchangedb/0001.sql +++ /dev/null @@ -1,368 +0,0 @@ --- --- This file is part of TALER --- Copyright (C) 2014--2020 Taler Systems SA --- --- TALER is free software; you can redistribute it and/or modify it under the --- terms of the GNU General 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 General Public License for more details. --- --- You should have received a copy of the GNU General Public License along with --- TALER; see the file COPYING. If not, see --- - --- Everything in one big transaction -BEGIN; - --- Check patch versioning is in place. -SELECT _v.register_patch('exchange-0001', NULL, NULL); - - --- Main denominations table. All the coins the exchange knows about. -CREATE TABLE IF NOT EXISTS denominations - (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) - ,denom_pub BYTEA NOT NULL - ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32) - ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) - ,valid_from INT8 NOT NULL - ,expire_withdraw INT8 NOT NULL - ,expire_deposit INT8 NOT NULL - ,expire_legal INT8 NOT NULL - ,coin_val INT8 NOT NULL - ,coin_frac INT4 NOT NULL - ,fee_withdraw_val INT8 NOT NULL - ,fee_withdraw_frac INT4 NOT NULL - ,fee_deposit_val INT8 NOT NULL - ,fee_deposit_frac INT4 NOT NULL - ,fee_refresh_val INT8 NOT NULL - ,fee_refresh_frac INT4 NOT NULL - ,fee_refund_val INT8 NOT NULL - ,fee_refund_frac INT4 NOT NULL - ); -CREATE INDEX IF NOT EXISTS denominations_expire_legal_index - ON denominations - (expire_legal); - --- denomination_revocations table is for remembering which denomination keys have been revoked -CREATE TABLE IF NOT EXISTS denomination_revocations - (denom_revocations_serial_id BIGSERIAL UNIQUE - ,denom_pub_hash BYTEA PRIMARY KEY REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE - ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) - ); --- reserves table is for summarization of a reserve. It is updated when new --- funds are added and existing funds are withdrawn. The 'expiration_date' --- can be used to eventually get rid of reserves that have not been used --- for a very long time (usually by refunding the owner) -CREATE TABLE IF NOT EXISTS reserves - (reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32) - ,account_details TEXT NOT NULL - ,current_balance_val INT8 NOT NULL - ,current_balance_frac INT4 NOT NULL - ,expiration_date INT8 NOT NULL - ,gc_date INT8 NOT NULL - ); --- index on reserves table (TODO: useless due to primary key!?) -CREATE INDEX IF NOT EXISTS reserves_reserve_pub_index - ON reserves - (reserve_pub); --- index for get_expired_reserves -CREATE INDEX IF NOT EXISTS reserves_expiration_index - ON reserves - (expiration_date - ,current_balance_val - ,current_balance_frac - ); --- index for reserve GC operations -CREATE INDEX IF NOT EXISTS reserves_gc_index - ON reserves - (gc_date); --- reserves_in table collects the transactions which transfer funds --- into the reserve. The rows of this table correspond to each --- incoming transaction. -CREATE TABLE IF NOT EXISTS reserves_in - (reserve_in_serial_id BIGSERIAL UNIQUE - ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE - ,wire_reference INT8 NOT NULL - ,credit_val INT8 NOT NULL - ,credit_frac INT4 NOT NULL - ,sender_account_details TEXT NOT NULL - ,exchange_account_section TEXT NOT NULL - ,execution_date INT8 NOT NULL - ,PRIMARY KEY (reserve_pub, wire_reference) - ); --- Create indices on reserves_in -CREATE INDEX IF NOT EXISTS reserves_in_execution_index - ON reserves_in - (exchange_account_section - ,execution_date - ); -CREATE INDEX IF NOT EXISTS reserves_in_exchange_account_serial - ON reserves_in - (exchange_account_section, - reserve_in_serial_id DESC - ); --- This table contains the data for wire transfers the exchange has --- executed to close a reserve. -CREATE TABLE IF NOT EXISTS reserves_close - (close_uuid BIGSERIAL PRIMARY KEY - ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE - ,execution_date INT8 NOT NULL - ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32) - ,receiver_account TEXT NOT NULL - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL - ,closing_fee_val INT8 NOT NULL - ,closing_fee_frac INT4 NOT NULL); -CREATE INDEX IF NOT EXISTS reserves_close_by_reserve - ON reserves_close - (reserve_pub); --- Table with the withdraw operations that have been performed on a reserve. --- The 'h_blind_ev' is the hash of the blinded coin. It serves as a primary --- key, as (broken) clients that use a non-random coin and blinding factor --- should fail to even withdraw, as otherwise the coins will fail to deposit --- (as they really must be unique). --- For the denom_pub, we do NOT CASCADE on DELETE, we may keep the denomination key alive! -CREATE TABLE IF NOT EXISTS reserves_out - (reserve_out_serial_id BIGSERIAL UNIQUE - ,h_blind_ev BYTEA PRIMARY KEY CHECK (LENGTH(h_blind_ev)=64) - ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) - ,denom_sig BYTEA NOT NULL - ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE - ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64) - ,execution_date INT8 NOT NULL - ,amount_with_fee_val INT8 NOT NULL - ,amount_with_fee_frac INT4 NOT NULL - ); --- Index blindcoins(reserve_pub) for get_reserves_out statement -CREATE INDEX IF NOT EXISTS reserves_out_reserve_pub_index - ON reserves_out - (reserve_pub); -CREATE INDEX IF NOT EXISTS reserves_out_execution_date - ON reserves_out - (execution_date); -CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info - ON reserves_out - (denom_pub_hash - ,h_blind_ev - ); --- Table with coins that have been (partially) spent, used to track --- coin information only once. -CREATE TABLE IF NOT EXISTS known_coins - (coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32) - ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE - ,denom_sig BYTEA NOT NULL - ); -CREATE INDEX IF NOT EXISTS known_coins_by_denomination - ON known_coins - (denom_pub_hash); --- Table with the commitments made when melting a coin. */ -CREATE TABLE IF NOT EXISTS refresh_commitments - (melt_serial_id BIGSERIAL UNIQUE - ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64) - ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE - ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64) - ,amount_with_fee_val INT8 NOT NULL - ,amount_with_fee_frac INT4 NOT NULL - ,noreveal_index INT4 NOT NULL - ); -CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index - ON refresh_commitments - (old_coin_pub); --- Table with the revelations about the new coins that are to be created --- during a melting session. Includes the session, the cut-and-choose --- index and the index of the new coin, and the envelope of the new --- coin to be signed, as well as the encrypted information about the --- private key and the blinding factor for the coin (for verification --- in case this newcoin_index is chosen to be revealed) -CREATE TABLE IF NOT EXISTS refresh_revealed_coins - (rc BYTEA NOT NULL REFERENCES refresh_commitments (rc) ON DELETE CASCADE - ,newcoin_index INT4 NOT NULL - ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64) - ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE - ,coin_ev BYTEA UNIQUE NOT NULL - ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64) - ,ev_sig BYTEA NOT NULL - ,PRIMARY KEY (rc, newcoin_index) - ,UNIQUE (h_coin_ev) - ); -CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index - ON refresh_revealed_coins - (denom_pub_hash); --- Table with the transfer keys of a refresh operation; includes --- the rc for which this is the link information, the --- transfer public key (for gamma) and the revealed transfer private --- keys (array of TALER_CNC_KAPPA - 1 entries, with gamma being skipped) */ -CREATE TABLE IF NOT EXISTS refresh_transfer_keys - (rc BYTEA NOT NULL PRIMARY KEY REFERENCES refresh_commitments (rc) ON DELETE CASCADE - ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32) - ,transfer_privs BYTEA NOT NULL - ); --- for get_link (not sure if this helps, as there should be very few --- transfer_pubs per rc, but at least in theory this helps the ORDER BY --- clause. -CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub - ON refresh_transfer_keys - (rc - ,transfer_pub - ); --- This table contains the wire transfers the exchange is supposed to --- execute to transmit funds to the merchants (and manage refunds). -CREATE TABLE IF NOT EXISTS deposits - (deposit_serial_id BIGSERIAL PRIMARY KEY - ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE - ,amount_with_fee_val INT8 NOT NULL - ,amount_with_fee_frac INT4 NOT NULL - ,timestamp INT8 NOT NULL - ,refund_deadline INT8 NOT NULL - ,wire_deadline INT8 NOT NULL - ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) - ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) - ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64) - ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64) - ,wire TEXT NOT NULL - ,tiny BOOLEAN NOT NULL DEFAULT FALSE - ,done BOOLEAN NOT NULL DEFAULT FALSE - ,UNIQUE (coin_pub, merchant_pub, h_contract_terms) - ); --- Index for get_deposit_for_wtid and get_deposit_statement */ -CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index - ON deposits - (coin_pub - ,merchant_pub - ,h_contract_terms - ); --- Index for deposits_get_ready -CREATE INDEX IF NOT EXISTS deposits_get_ready_index - ON deposits - (tiny - ,done - ,wire_deadline - ,refund_deadline - ); --- Index for deposits_iterate_matching -CREATE INDEX IF NOT EXISTS deposits_iterate_matching - ON deposits - (merchant_pub - ,h_wire - ,done - ,wire_deadline - ); --- Table with information about coins that have been refunded. (Technically --- one of the deposit operations that a coin was involved with is refunded.) --- The combo of coin_pub, merchant_pub, h_contract_terms and rtransaction_id --- MUST be unique, and we usually select by coin_pub so that one goes first. */ -CREATE TABLE IF NOT EXISTS refunds - (refund_serial_id BIGSERIAL UNIQUE - ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE - ,merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32) - ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64) - ,h_contract_terms BYTEA NOT NULL CHECK(LENGTH(h_contract_terms)=64) - ,rtransaction_id INT8 NOT NULL - ,amount_with_fee_val INT8 NOT NULL - ,amount_with_fee_frac INT4 NOT NULL - ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id) - ); -CREATE INDEX IF NOT EXISTS refunds_coin_pub_index - ON refunds - (coin_pub); --- This table contains the data for --- wire transfers the exchange has executed. -CREATE TABLE IF NOT EXISTS wire_out - (wireout_uuid BIGSERIAL PRIMARY KEY - ,execution_date INT8 NOT NULL - ,wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32) - ,wire_target TEXT NOT NULL - ,exchange_account_section TEXT NOT NULL - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL - ); --- Table for the tracking API, mapping from wire transfer identifier --- to transactions and back -CREATE TABLE IF NOT EXISTS aggregation_tracking - (aggregation_serial_id BIGSERIAL UNIQUE - ,deposit_serial_id INT8 PRIMARY KEY REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE - ,wtid_raw BYTEA CONSTRAINT wire_out_ref REFERENCES wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE - ); --- Index for lookup_transactions statement on wtid -CREATE INDEX IF NOT EXISTS aggregation_tracking_wtid_index - ON aggregation_tracking - (wtid_raw); --- Table for the wire fees. -CREATE TABLE IF NOT EXISTS wire_fee - (wire_method VARCHAR NOT NULL - ,start_date INT8 NOT NULL - ,end_date INT8 NOT NULL - ,wire_fee_val INT8 NOT NULL - ,wire_fee_frac INT4 NOT NULL - ,closing_fee_val INT8 NOT NULL - ,closing_fee_frac INT4 NOT NULL - ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) - ,PRIMARY KEY (wire_method, start_date) - ); -CREATE INDEX IF NOT EXISTS wire_fee_gc_index - ON wire_fee - (end_date); --- Table for /recoup information --- Do not cascade on the coin_pub, as we may keep the coin alive! */ -CREATE TABLE IF NOT EXISTS recoup - (recoup_uuid BIGSERIAL UNIQUE - ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) - ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) - ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL - ,timestamp INT8 NOT NULL - ,h_blind_ev BYTEA NOT NULL REFERENCES reserves_out (h_blind_ev) ON DELETE CASCADE - ); -CREATE INDEX IF NOT EXISTS recoup_by_coin_index - ON recoup - (coin_pub); -CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev - ON recoup - (h_blind_ev); -CREATE INDEX IF NOT EXISTS recoup_for_by_reserve - ON recoup - (coin_pub - ,h_blind_ev - ); --- Table for /recoup-refresh information --- Do not cascade on the coin_pub, as we may keep the coin alive! */ -CREATE TABLE IF NOT EXISTS recoup_refresh - (recoup_refresh_uuid BIGSERIAL UNIQUE - ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) - ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) - ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) - ,amount_val INT8 NOT NULL - ,amount_frac INT4 NOT NULL - ,timestamp INT8 NOT NULL - ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE - ); -CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index - ON recoup_refresh - (coin_pub); -CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev - ON recoup_refresh - (h_blind_ev); -CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve - ON recoup_refresh - (coin_pub - ,h_blind_ev - ); --- This table contains the pre-commit data for --- wire transfers the exchange is about to execute. -CREATE TABLE IF NOT EXISTS prewire - (prewire_uuid BIGSERIAL PRIMARY KEY - ,type TEXT NOT NULL - ,finished BOOLEAN NOT NULL DEFAULT false - ,buf BYTEA NOT NULL - ); --- Index for wire_prepare_data_get and gc_prewire statement -CREATE INDEX IF NOT EXISTS prepare_iteration_index - ON prewire - (finished); - --- Complete transaction -COMMIT; diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index e7ac4d719..37809f6b4 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -15,8 +15,8 @@ pkgcfg_DATA = \ sqldir = $(prefix)/share/taler/sql/exchange/ sql_DATA = \ - 0000.sql \ - 0001.sql \ + exchange-0000.sql \ + exchange-0001.sql \ drop0000.sql EXTRA_DIST = \ diff --git a/src/exchangedb/exchange-0000.sql b/src/exchangedb/exchange-0000.sql new file mode 100644 index 000000000..1483e2015 --- /dev/null +++ b/src/exchangedb/exchange-0000.sql @@ -0,0 +1,293 @@ +-- LICENSE AND COPYRIGHT +-- +-- Copyright (C) 2010 Hubert depesz Lubaczewski +-- +-- This program is distributed under the (Revised) BSD License: +-- L +-- +-- Redistribution and use in source and binary forms, with or without +-- modification, are permitted provided that the following conditions +-- are met: +-- +-- * Redistributions of source code must retain the above copyright +-- notice, this list of conditions and the following disclaimer. +-- +-- * Redistributions in binary form must reproduce the above copyright +-- notice, this list of conditions and the following disclaimer in the +-- documentation and/or other materials provided with the distribution. +-- +-- * Neither the name of Hubert depesz Lubaczewski's Organization +-- nor the names of its contributors may be used to endorse or +-- promote products derived from this software without specific +-- prior written permission. +-- +-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-- +-- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql +-- +-- +-- # NAME +-- +-- **Versioning** - simplistic take on tracking and applying changes to databases. +-- +-- # DESCRIPTION +-- +-- This project strives to provide simple way to manage changes to +-- database. +-- +-- Instead of making changes on development server, then finding +-- differences between production and development, deciding which ones +-- should be installed on production, and finding a way to install them - +-- you start with writing diffs themselves! +-- +-- # INSTALLATION +-- +-- To install versioning simply run install.versioning.sql in your database +-- (all of them: production, stage, test, devel, ...). +-- +-- # USAGE +-- +-- In your files with patches to database, put whole logic in single +-- transaction, and use \_v.\* functions - usually \_v.register_patch() at +-- least to make sure everything is OK. +-- +-- For example. Let's assume you have patch files: +-- +-- ## 0001.sql: +-- +-- ``` +-- create table users (id serial primary key, username text); +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- insert into users (username) values ('depesz'); +-- ``` +-- To change it to use versioning you would change the files, to this +-- state: +-- +-- 0000.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('000-base', NULL, NULL); +-- create table users (id serial primary key, username text); +-- COMMIT; +-- ``` +-- +-- ## 0002.sql: +-- +-- ``` +-- BEGIN; +-- select _v.register_patch('001-users', ARRAY['000-base'], NULL); +-- insert into users (username) values ('depesz'); +-- COMMIT; +-- ``` +-- +-- This will make sure that patch 001-users can only be applied after +-- 000-base. +-- +-- # AVAILABLE FUNCTIONS +-- +-- ## \_v.register_patch( TEXT ) +-- +-- Registers named patch, or dies if it is already registered. +-- +-- Returns integer which is id of patch in \_v.patches table - only if it +-- succeeded. +-- +-- ## \_v.register_patch( TEXT, TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as +-- array in second argument) are already registered. +-- +-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] ) +-- +-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches. +-- +-- Third argument is array of names of patches that conflict with current one. So +-- if any of them is installed - register_patch will error out. +-- +-- ## \_v.unregister_patch( TEXT ) +-- +-- Removes information about given patch from the versioning data. +-- +-- It doesn't remove objects that were created by this patch - just removes +-- metainformation. +-- +-- ## \_v.assert_user_is_superuser() +-- +-- Make sure that current patch is being loaded by superuser. +-- +-- If it's not - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_not_superuser() +-- +-- Make sure that current patch is not being loaded by superuser. +-- +-- If it is - it will raise exception, and break transaction. +-- +-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... ) +-- +-- Make sure that current patch is being loaded by one of listed users. +-- +-- If ```current_user``` is not listed as one of arguments - function will raise +-- exception and break the transaction. + +BEGIN; + +-- This file adds versioning support to database it will be loaded to. +-- It requires that PL/pgSQL is already loaded - will raise exception otherwise. +-- All versioning "stuff" (tables, functions) is in "_v" schema. + +-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literaly nothing (0 rows). +-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling. +CREATE SCHEMA IF NOT EXISTS _v; +COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.'; + +CREATE TABLE IF NOT EXISTS _v.patches ( + patch_name TEXT PRIMARY KEY, + applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(), + applied_by TEXT NOT NULL, + requires TEXT[], + conflicts TEXT[] +); +COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.'; +COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.'; +COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.'; +COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)'; +COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.'; +COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + t_text TEXT; + t_text_a TEXT[]; + i INT4; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF FOUND THEN + RAISE EXCEPTION 'Patch % is already applied!', in_patch_name; + END IF; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' ); + END IF; + + IF array_upper( in_requirements, 1 ) IS NOT NULL THEN + t_text_a := '{}'; + FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i]; + IF NOT FOUND THEN + t_text_a := t_text_a || in_requirements[i]; + END IF; + END LOOP; + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' ); + END IF; + END IF; + + INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) ); + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.'; + +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, $2, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.'; +CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$ + SELECT _v.register_patch( $1, NULL, NULL ); +$$ language sql; +COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.'; + +CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$ +DECLARE + i INT4; + t_text_a TEXT[]; +BEGIN + -- Thanks to this we know only one patch will be applied at a time + LOCK TABLE _v.patches IN EXCLUSIVE MODE; + + t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) ); + IF array_upper( t_text_a, 1 ) IS NOT NULL THEN + RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' ); + END IF; + + DELETE FROM _v.patches WHERE patch_name = in_patch_name; + GET DIAGNOSTICS i = ROW_COUNT; + IF i < 1 THEN + RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name; + END IF; + + RETURN; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.'; + +CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$ +DECLARE + t_text TEXT; +BEGIN + SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name; + IF NOT FOUND THEN + RAISE EXCEPTION 'Patch % is not applied!', in_patch_name; + END IF; + RETURN format('Patch %s is applied.', in_patch_name); +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RETURN 'assert_user_is_superuser: OK'; + END IF; + RAISE EXCEPTION 'Current user is not superuser - cannot continue.'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$ +DECLARE + v_super bool; +BEGIN + SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user; + IF v_super THEN + RAISE EXCEPTION 'Current user is superuser - cannot continue.'; + END IF; + RETURN 'assert_user_is_not_superuser: OK'; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.'; + +CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$ +DECLARE +BEGIN + IF current_user = any( p_acceptable_users ) THEN + RETURN 'assert_user_is_one_of: OK'; + END IF; + RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users; +END; +$$ language plpgsql; +COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.'; + +COMMIT; diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql new file mode 100644 index 000000000..02dc68cf4 --- /dev/null +++ b/src/exchangedb/exchange-0001.sql @@ -0,0 +1,368 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2014--2020 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General 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 General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('exchange-0001', NULL, NULL); + + +-- Main denominations table. All the coins the exchange knows about. +CREATE TABLE IF NOT EXISTS denominations + (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) + ,denom_pub BYTEA NOT NULL + ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32) + ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) + ,valid_from INT8 NOT NULL + ,expire_withdraw INT8 NOT NULL + ,expire_deposit INT8 NOT NULL + ,expire_legal INT8 NOT NULL + ,coin_val INT8 NOT NULL + ,coin_frac INT4 NOT NULL + ,fee_withdraw_val INT8 NOT NULL + ,fee_withdraw_frac INT4 NOT NULL + ,fee_deposit_val INT8 NOT NULL + ,fee_deposit_frac INT4 NOT NULL + ,fee_refresh_val INT8 NOT NULL + ,fee_refresh_frac INT4 NOT NULL + ,fee_refund_val INT8 NOT NULL + ,fee_refund_frac INT4 NOT NULL + ); +CREATE INDEX IF NOT EXISTS denominations_expire_legal_index + ON denominations + (expire_legal); + +-- denomination_revocations table is for remembering which denomination keys have been revoked +CREATE TABLE IF NOT EXISTS denomination_revocations + (denom_revocations_serial_id BIGSERIAL UNIQUE + ,denom_pub_hash BYTEA PRIMARY KEY REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE + ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) + ); +-- reserves table is for summarization of a reserve. It is updated when new +-- funds are added and existing funds are withdrawn. The 'expiration_date' +-- can be used to eventually get rid of reserves that have not been used +-- for a very long time (usually by refunding the owner) +CREATE TABLE IF NOT EXISTS reserves + (reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32) + ,account_details TEXT NOT NULL + ,current_balance_val INT8 NOT NULL + ,current_balance_frac INT4 NOT NULL + ,expiration_date INT8 NOT NULL + ,gc_date INT8 NOT NULL + ); +-- index on reserves table (TODO: useless due to primary key!?) +CREATE INDEX IF NOT EXISTS reserves_reserve_pub_index + ON reserves + (reserve_pub); +-- index for get_expired_reserves +CREATE INDEX IF NOT EXISTS reserves_expiration_index + ON reserves + (expiration_date + ,current_balance_val + ,current_balance_frac + ); +-- index for reserve GC operations +CREATE INDEX IF NOT EXISTS reserves_gc_index + ON reserves + (gc_date); +-- reserves_in table collects the transactions which transfer funds +-- into the reserve. The rows of this table correspond to each +-- incoming transaction. +CREATE TABLE IF NOT EXISTS reserves_in + (reserve_in_serial_id BIGSERIAL UNIQUE + ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE + ,wire_reference INT8 NOT NULL + ,credit_val INT8 NOT NULL + ,credit_frac INT4 NOT NULL + ,sender_account_details TEXT NOT NULL + ,exchange_account_section TEXT NOT NULL + ,execution_date INT8 NOT NULL + ,PRIMARY KEY (reserve_pub, wire_reference) + ); +-- Create indices on reserves_in +CREATE INDEX IF NOT EXISTS reserves_in_execution_index + ON reserves_in + (exchange_account_section + ,execution_date + ); +CREATE INDEX IF NOT EXISTS reserves_in_exchange_account_serial + ON reserves_in + (exchange_account_section, + reserve_in_serial_id DESC + ); +-- This table contains the data for wire transfers the exchange has +-- executed to close a reserve. +CREATE TABLE IF NOT EXISTS reserves_close + (close_uuid BIGSERIAL PRIMARY KEY + ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE + ,execution_date INT8 NOT NULL + ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32) + ,receiver_account TEXT NOT NULL + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ,closing_fee_val INT8 NOT NULL + ,closing_fee_frac INT4 NOT NULL); +CREATE INDEX IF NOT EXISTS reserves_close_by_reserve + ON reserves_close + (reserve_pub); +-- Table with the withdraw operations that have been performed on a reserve. +-- The 'h_blind_ev' is the hash of the blinded coin. It serves as a primary +-- key, as (broken) clients that use a non-random coin and blinding factor +-- should fail to even withdraw, as otherwise the coins will fail to deposit +-- (as they really must be unique). +-- For the denom_pub, we do NOT CASCADE on DELETE, we may keep the denomination key alive! +CREATE TABLE IF NOT EXISTS reserves_out + (reserve_out_serial_id BIGSERIAL UNIQUE + ,h_blind_ev BYTEA PRIMARY KEY CHECK (LENGTH(h_blind_ev)=64) + ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) + ,denom_sig BYTEA NOT NULL + ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE + ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64) + ,execution_date INT8 NOT NULL + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ); +-- Index blindcoins(reserve_pub) for get_reserves_out statement +CREATE INDEX IF NOT EXISTS reserves_out_reserve_pub_index + ON reserves_out + (reserve_pub); +CREATE INDEX IF NOT EXISTS reserves_out_execution_date + ON reserves_out + (execution_date); +CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info + ON reserves_out + (denom_pub_hash + ,h_blind_ev + ); +-- Table with coins that have been (partially) spent, used to track +-- coin information only once. +CREATE TABLE IF NOT EXISTS known_coins + (coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32) + ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE + ,denom_sig BYTEA NOT NULL + ); +CREATE INDEX IF NOT EXISTS known_coins_by_denomination + ON known_coins + (denom_pub_hash); +-- Table with the commitments made when melting a coin. */ +CREATE TABLE IF NOT EXISTS refresh_commitments + (melt_serial_id BIGSERIAL UNIQUE + ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64) + ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE + ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64) + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,noreveal_index INT4 NOT NULL + ); +CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index + ON refresh_commitments + (old_coin_pub); +-- Table with the revelations about the new coins that are to be created +-- during a melting session. Includes the session, the cut-and-choose +-- index and the index of the new coin, and the envelope of the new +-- coin to be signed, as well as the encrypted information about the +-- private key and the blinding factor for the coin (for verification +-- in case this newcoin_index is chosen to be revealed) +CREATE TABLE IF NOT EXISTS refresh_revealed_coins + (rc BYTEA NOT NULL REFERENCES refresh_commitments (rc) ON DELETE CASCADE + ,newcoin_index INT4 NOT NULL + ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64) + ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE + ,coin_ev BYTEA UNIQUE NOT NULL + ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64) + ,ev_sig BYTEA NOT NULL + ,PRIMARY KEY (rc, newcoin_index) + ,UNIQUE (h_coin_ev) + ); +CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index + ON refresh_revealed_coins + (denom_pub_hash); +-- Table with the transfer keys of a refresh operation; includes +-- the rc for which this is the link information, the +-- transfer public key (for gamma) and the revealed transfer private +-- keys (array of TALER_CNC_KAPPA - 1 entries, with gamma being skipped) */ +CREATE TABLE IF NOT EXISTS refresh_transfer_keys + (rc BYTEA NOT NULL PRIMARY KEY REFERENCES refresh_commitments (rc) ON DELETE CASCADE + ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32) + ,transfer_privs BYTEA NOT NULL + ); +-- for get_link (not sure if this helps, as there should be very few +-- transfer_pubs per rc, but at least in theory this helps the ORDER BY +-- clause. +CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub + ON refresh_transfer_keys + (rc + ,transfer_pub + ); +-- This table contains the wire transfers the exchange is supposed to +-- execute to transmit funds to the merchants (and manage refunds). +CREATE TABLE IF NOT EXISTS deposits + (deposit_serial_id BIGSERIAL PRIMARY KEY + ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,timestamp INT8 NOT NULL + ,refund_deadline INT8 NOT NULL + ,wire_deadline INT8 NOT NULL + ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) + ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) + ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64) + ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64) + ,wire TEXT NOT NULL + ,tiny BOOLEAN NOT NULL DEFAULT FALSE + ,done BOOLEAN NOT NULL DEFAULT FALSE + ,UNIQUE (coin_pub, merchant_pub, h_contract_terms) + ); +-- Index for get_deposit_for_wtid and get_deposit_statement */ +CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index + ON deposits + (coin_pub + ,merchant_pub + ,h_contract_terms + ); +-- Index for deposits_get_ready +CREATE INDEX IF NOT EXISTS deposits_get_ready_index + ON deposits + (tiny + ,done + ,wire_deadline + ,refund_deadline + ); +-- Index for deposits_iterate_matching +CREATE INDEX IF NOT EXISTS deposits_iterate_matching + ON deposits + (merchant_pub + ,h_wire + ,done + ,wire_deadline + ); +-- Table with information about coins that have been refunded. (Technically +-- one of the deposit operations that a coin was involved with is refunded.) +-- The combo of coin_pub, merchant_pub, h_contract_terms and rtransaction_id +-- MUST be unique, and we usually select by coin_pub so that one goes first. */ +CREATE TABLE IF NOT EXISTS refunds + (refund_serial_id BIGSERIAL UNIQUE + ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE + ,merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32) + ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64) + ,h_contract_terms BYTEA NOT NULL CHECK(LENGTH(h_contract_terms)=64) + ,rtransaction_id INT8 NOT NULL + ,amount_with_fee_val INT8 NOT NULL + ,amount_with_fee_frac INT4 NOT NULL + ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id) + ); +CREATE INDEX IF NOT EXISTS refunds_coin_pub_index + ON refunds + (coin_pub); +-- This table contains the data for +-- wire transfers the exchange has executed. +CREATE TABLE IF NOT EXISTS wire_out + (wireout_uuid BIGSERIAL PRIMARY KEY + ,execution_date INT8 NOT NULL + ,wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32) + ,wire_target TEXT NOT NULL + ,exchange_account_section TEXT NOT NULL + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ); +-- Table for the tracking API, mapping from wire transfer identifier +-- to transactions and back +CREATE TABLE IF NOT EXISTS aggregation_tracking + (aggregation_serial_id BIGSERIAL UNIQUE + ,deposit_serial_id INT8 PRIMARY KEY REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE + ,wtid_raw BYTEA CONSTRAINT wire_out_ref REFERENCES wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE + ); +-- Index for lookup_transactions statement on wtid +CREATE INDEX IF NOT EXISTS aggregation_tracking_wtid_index + ON aggregation_tracking + (wtid_raw); +-- Table for the wire fees. +CREATE TABLE IF NOT EXISTS wire_fee + (wire_method VARCHAR NOT NULL + ,start_date INT8 NOT NULL + ,end_date INT8 NOT NULL + ,wire_fee_val INT8 NOT NULL + ,wire_fee_frac INT4 NOT NULL + ,closing_fee_val INT8 NOT NULL + ,closing_fee_frac INT4 NOT NULL + ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) + ,PRIMARY KEY (wire_method, start_date) + ); +CREATE INDEX IF NOT EXISTS wire_fee_gc_index + ON wire_fee + (end_date); +-- Table for /recoup information +-- Do not cascade on the coin_pub, as we may keep the coin alive! */ +CREATE TABLE IF NOT EXISTS recoup + (recoup_uuid BIGSERIAL UNIQUE + ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) + ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) + ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ,timestamp INT8 NOT NULL + ,h_blind_ev BYTEA NOT NULL REFERENCES reserves_out (h_blind_ev) ON DELETE CASCADE + ); +CREATE INDEX IF NOT EXISTS recoup_by_coin_index + ON recoup + (coin_pub); +CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev + ON recoup + (h_blind_ev); +CREATE INDEX IF NOT EXISTS recoup_for_by_reserve + ON recoup + (coin_pub + ,h_blind_ev + ); +-- Table for /recoup-refresh information +-- Do not cascade on the coin_pub, as we may keep the coin alive! */ +CREATE TABLE IF NOT EXISTS recoup_refresh + (recoup_refresh_uuid BIGSERIAL UNIQUE + ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) + ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) + ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) + ,amount_val INT8 NOT NULL + ,amount_frac INT4 NOT NULL + ,timestamp INT8 NOT NULL + ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE + ); +CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index + ON recoup_refresh + (coin_pub); +CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev + ON recoup_refresh + (h_blind_ev); +CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve + ON recoup_refresh + (coin_pub + ,h_blind_ev + ); +-- This table contains the pre-commit data for +-- wire transfers the exchange is about to execute. +CREATE TABLE IF NOT EXISTS prewire + (prewire_uuid BIGSERIAL PRIMARY KEY + ,type TEXT NOT NULL + ,finished BOOLEAN NOT NULL DEFAULT false + ,buf BYTEA NOT NULL + ); +-- Index for wire_prepare_data_get and gc_prewire statement +CREATE INDEX IF NOT EXISTS prepare_iteration_index + ON prewire + (finished); + +-- Complete transaction +COMMIT; diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index fe0021fa6..9df2fe770 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -113,12 +113,6 @@ struct PostgresClosure */ const struct GNUNET_CONFIGURATION_Handle *cfg; - /** - * Database connection string, as read from - * the configuration. - */ - char *connection_cfg_str; - /** * Directory with SQL statements to run to create tables. */ @@ -163,16 +157,12 @@ postgres_drop_tables (void *cls) { struct PostgresClosure *pc = cls; struct GNUNET_PQ_Context *conn; - char *drop_dir; - - GNUNET_asprintf (&drop_dir, - "%sdrop", - pc->sql_dir); - conn = GNUNET_PQ_connect (pc->connection_cfg_str, - drop_dir, - NULL, - NULL); - GNUNET_free (drop_dir); + + conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "exchangedb-postgres", + "drop", + NULL, + NULL); if (NULL == conn) return GNUNET_SYSERR; GNUNET_PQ_disconnect (conn); @@ -194,7 +184,7 @@ postgres_create_tables (void *cls) conn = GNUNET_PQ_connect_with_cfg (pc->cfg, "exchangedb-postgres", - "", + "exchange-", NULL, NULL); if (NULL == conn) @@ -1393,10 +1383,11 @@ postgres_get_session (void *cls) GNUNET_PQ_PREPARED_STATEMENT_END }; - db_conn = GNUNET_PQ_connect (pc->connection_cfg_str, - NULL, - es, - ps); + db_conn = GNUNET_PQ_connect_with_cfg (pc->cfg, + "exchangedb-postgres", + NULL, + es, + ps); } if (NULL == db_conn) return NULL; @@ -5377,10 +5368,11 @@ postgres_gc (void *cls) GNUNET_PQ_PREPARED_STATEMENT_END }; - conn = GNUNET_PQ_connect (pg->connection_cfg_str, - NULL, - NULL, - ps); + conn = GNUNET_PQ_connect_with_cfg (pg->cfg, + "exchangedb-postgres", + NULL, + NULL, + ps); } if (NULL == conn) return GNUNET_SYSERR; @@ -7221,7 +7213,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct PostgresClosure *pg; struct TALER_EXCHANGEDB_Plugin *plugin; - const char *ec; pg = GNUNET_new (struct PostgresClosure); pg->cfg = cfg; @@ -7246,28 +7237,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) GNUNET_free (pg); return NULL; } - ec = getenv ("TALER_EXCHANGEDB_POSTGRES_CONFIG"); - if (NULL != ec) - { - pg->connection_cfg_str = GNUNET_strdup (ec); - } - else - { - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "exchangedb-postgres", - "CONFIG", - &pg->connection_cfg_str)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "exchangedb-postgres", - "CONFIG"); - GNUNET_free (pg->sql_dir); - GNUNET_free (pg); - return NULL; - } - } - if ( (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, "exchangedb", @@ -7283,7 +7252,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchangedb", "LEGAL/IDLE_RESERVE_EXPIRATION_TIME"); - GNUNET_free (pg->connection_cfg_str); GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; @@ -7297,7 +7265,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "CURRENCY"); - GNUNET_free (pg->connection_cfg_str); GNUNET_free (pg->sql_dir); GNUNET_free (pg); return NULL; @@ -7413,7 +7380,6 @@ libtaler_plugin_exchangedb_postgres_done (void *cls) /* If we launched a session for the main thread, kill it here before we unload */ db_conn_destroy (pg->main_session); - GNUNET_free (pg->connection_cfg_str); GNUNET_free (pg->sql_dir); GNUNET_free (pg->currency); GNUNET_free (pg); -- cgit v1.2.3