donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 74388fd3718dc67a7fbf712f6983d71443685b7f
parent acd6092a1acc43f3efbf6cf0a539341ee933baed
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  7 Aug 2026 00:32:02 +0200

fix warning

Diffstat:
Msrc/donaudb/meson.build | 1+
Msrc/donaudb/test_donaudb.c | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 188 insertions(+), 10 deletions(-)

diff --git a/src/donaudb/meson.build b/src/donaudb/meson.build @@ -135,6 +135,7 @@ test_donaudb = executable( talerpq_dep, gnunetutil_dep, gnunetjson_dep, + gnunetpq_dep, pq_dep, json_dep, ], diff --git a/src/donaudb/test_donaudb.c b/src/donaudb/test_donaudb.c @@ -98,6 +98,120 @@ static int result; static struct DONAUDB_PostgresContext *ctx; /** + * Denomination key pair used to manufacture blinded donation unit + * signatures for the regression tests below. Created on demand. + */ +static struct TALER_DenominationPrivateKey test_denom_priv; + +/** + * Public key matching #test_denom_priv. + */ +static struct TALER_DenominationPublicKey test_denom_pub; + + +/** + * Create a blinded donation unit signature usable as filler data + * for `receipts_issued.blinded_sig'. + * + * @return freshly allocated blinded signature + */ +static struct GNUNET_CRYPTO_BlindedSignature * +make_blinded_sig (void) +{ + struct GNUNET_CRYPTO_BlindedMessage *rp; + struct GNUNET_CRYPTO_BlindedSignature *bs; + + if (NULL == test_denom_priv.bsign_priv_key) + GNUNET_assert (GNUNET_OK == + TALER_denom_priv_create (&test_denom_priv, + &test_denom_pub, + GNUNET_CRYPTO_BSA_RSA, + RSA_KEY_SIZE)); + rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage); + rp->cipher = GNUNET_CRYPTO_BSA_RSA; + rp->rc = 1; + rp->details.rsa_blinded_message.blinded_msg_size = 32; + rp->details.rsa_blinded_message.blinded_msg = GNUNET_malloc (32); + GNUNET_CRYPTO_random_block (rp->details.rsa_blinded_message.blinded_msg, + 32); + bs = GNUNET_CRYPTO_blind_sign (test_denom_priv.bsign_priv_key, + "rw", + rp); + GNUNET_assert (NULL != bs); + GNUNET_CRYPTO_blinded_message_decref (rp); + return bs; +} + + +/** + * Register a fresh charity with a random public key. + * + * @param max_per_year annual donation limit to use + * @param[out] charity_id set to the ID of the new charity + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +make_charity (const char *max_per_year, + uint64_t *charity_id) +{ + struct DONAU_CharityPublicKeyP charity_pub; + struct TALER_Amount max; + + RND_BLK (&charity_pub); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (max_per_year, + &max)); + return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == + DONAUDB_insert_charity (ctx, + &charity_pub, + "test charity", + "https://charity.example.com/", + &max, + charity_id)) + ? GNUNET_OK + : GNUNET_SYSERR; +} + + +/** + * Issue one (fresh, never seen before) receipt for @a charity_id. + * + * @param charity_id charity to issue for + * @param year year to attribute the receipt to + * @param amount amount of the receipt + * @param[out] under_limit set to true if the charity stayed below its limit + * @return database status of the operation + */ +static enum GNUNET_DB_QueryStatus +issue_receipt (uint64_t charity_id, + uint32_t year, + const char *amount, + bool *under_limit) +{ + struct DONAU_BlindedDonationUnitSignature du_sigs[1]; + struct DONAU_DonationReceiptHashP h_receipt; + struct TALER_Amount amt; + enum GNUNET_DB_QueryStatus qs; + + RND_BLK (&h_receipt); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (amount, + &amt)); + du_sigs[0].blinded_sig = make_blinded_sig (); + qs = DONAUDB_do_insert_receipt_issued (ctx, + year, + 1, + du_sigs, + charity_id, + &h_receipt, + &amt, + under_limit); + GNUNET_CRYPTO_blinded_sig_decref (du_sigs[0].blinded_sig); + return qs; +} + + +/** * Return charities information. * * @param cls closure @@ -765,17 +879,17 @@ run (void *cls) struct TALER_Amount expected; size_t conflict_index = 42; size_t unknown_index = 42; - uint64_t year = GNUNET_TIME_get_current_year (); + uint64_t this_year = GNUNET_TIME_get_current_year (); RND_BLK (&donor); RND_BLK (&nonce); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != make_donation_unit (&du_a, - year, + this_year, CURRENCY ":10")); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != make_donation_unit (&du_b, - year, + this_year, CURRENCY ":25")); dr.h_donation_unit_pub = du_a; @@ -786,7 +900,7 @@ run (void *cls) &donor, 1, &dr, - year, + this_year, &conflict_index, &unknown_index)); FAILIF (1 != conflict_index); /* == num_dr, i.e. no conflict */ @@ -801,7 +915,7 @@ run (void *cls) &donor, 1, &dr, - year, + this_year, &conflict_index, &unknown_index)); FAILIF (0 != conflict_index); @@ -810,7 +924,7 @@ run (void *cls) /* Only the first receipt is deductible; the second one was dropped. */ FAILIF (0 > DONAUDB_get_receipts_submitted_total (ctx, - year, + this_year, &donor, &total)); GNUNET_assert (GNUNET_OK == @@ -833,12 +947,12 @@ run (void *cls) struct TALER_Amount zero; size_t conflict_index = 42; size_t unknown_index = 42; - uint64_t year = GNUNET_TIME_get_current_year (); + uint64_t this_year = GNUNET_TIME_get_current_year (); RND_BLK (&donor); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != make_donation_unit (&known_du, - year, + this_year, CURRENCY ":10")); drs[0].h_donation_unit_pub = known_du; RND_BLK (&drs[0].nonce); @@ -853,7 +967,7 @@ run (void *cls) &donor, 2, drs, - year, + this_year, &conflict_index, &unknown_index)); FAILIF (1 != unknown_index); @@ -863,7 +977,7 @@ run (void *cls) /* The valid receipt in the same batch must NOT have been stored. */ FAILIF (0 > DONAUDB_get_receipts_submitted_total (ctx, - year, + this_year, &donor, &total)); GNUNET_assert (GNUNET_OK == @@ -873,6 +987,64 @@ run (void *cls) &zero)); } + /* Everything below runs in autocommit, which is what donau-httpd does. */ + + /* D-1: amount_add()'s overflow guard must trigger at 2^52, not at + 2^20. A charity with a realistic annual limit must be able to + book receipts well beyond 1048576 units of currency. */ + { + uint64_t big_charity_id; + bool under_limit = false; + + FAILIF (GNUNET_OK != + make_charity (CURRENCY ":4000000000", + &big_charity_id)); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + issue_receipt (big_charity_id, + GNUNET_TIME_get_current_year (), + CURRENCY ":2000000", + &under_limit)); + FAILIF (! under_limit); + } + + /* D-8: receipts_issued.receipt_id must not be settable from outside. + If it is, an operator bulk load or a partial restore can leave the + identity sequence behind the largest stored value, after which every + insert fails on receipts_issued_receipt_id_key -- which donau-httpd + used to answer with an assertion failure, i.e. abort(). */ + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + + /* Control: the very same insert without receipt_id is accepted. */ + FAILIF (GNUNET_OK != + GNUNET_PQ_prepare_anon ( + ctx->conn, + "INSERT INTO receipts_issued" + " (blinded_sig,charity_id,receipt_hash,amount)" + " SELECT r.blinded_sig, r.charity_id," + " sha512(r.receipt_hash), r.amount" + " FROM receipts_issued r" + " LIMIT 1;")); + /* Supplying receipt_id explicitly must be refused. With GENERATED + ALWAYS PostgreSQL already rejects it at PREPARE time; if a server + accepts the statement, executing it must fail. */ + if (GNUNET_OK == + GNUNET_PQ_prepare_anon ( + ctx->conn, + "INSERT INTO receipts_issued" + " (receipt_id,blinded_sig,charity_id,receipt_hash,amount)" + " SELECT 424242, r.blinded_sig, r.charity_id," + " sha512(r.receipt_hash), r.amount" + " FROM receipts_issued r" + " LIMIT 1;")) + FAILIF (0 <= + GNUNET_PQ_eval_prepared_non_select (ctx->conn, + "", + params)); + } + result = 0; drop: @@ -881,6 +1053,11 @@ drop: GNUNET_break (GNUNET_OK == DONAUDB_drop_tables (ctx)); cleanup: + if (NULL != test_denom_priv.bsign_priv_key) + { + TALER_denom_priv_free (&test_denom_priv); + TALER_denom_pub_free (&test_denom_pub); + } DONAUDB_disconnect (ctx); ctx = NULL; }