donau

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

test_donaudb.c (48668B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file src/donaudb/test_donaudb.c
     18  * @brief test cases for DB interaction functions
     19  * @author Johannes Casaburi
     20  */
     21 #include "donau_config.h"
     22 #include <taler/taler_json_lib.h>
     23 #include "donaudb_lib.h"
     24 #include "donau_util.h"
     25 #include "helper.h"
     26 #include "donaudb_lib.h"
     27 #include "donau-database/commit.h"
     28 #include "donau-database/create_tables.h"
     29 #include "donau-database/delete_charity.h"
     30 #include "donau-database/drop_tables.h"
     31 #include "donau-database/event_listen_cancel.h"
     32 #include "donau-database/event_listen.h"
     33 #include "donau-database/event_notify.h"
     34 #include "donau-database/iterate_charities.h"
     35 #include "donau-database/iterate_history_entries.h"
     36 #include "donau-database/insert_charity.h"
     37 #include "donau-database/insert_donation_unit.h"
     38 #include "donau-database/insert_history_entry.h"
     39 #include "donau-database/do_insert_receipt_issued.h"
     40 #include "donau-database/insert_signkey.h"
     41 #include "donau-database/insert_receipts_submitted.h"
     42 #include "donau-database/iterate_active_signkeys.h"
     43 #include "donau-database/iterate_donation_units.h"
     44 #include "donau-database/get_receipts_submitted_total.h"
     45 #include "donau-database/get_charity.h"
     46 #include "donau-database/get_donation_unit_amount.h"
     47 #include "donau-database/get_receipt_issued.h"
     48 #include "donau-database/get_signkey.h"
     49 #include "donau-database/preflight.h"
     50 #include "donau-database/rollback.h"
     51 #include "donau-database/start.h"
     52 #include "donau-database/start_read_committed.h"
     53 #include "donau-database/start_read_only.h"
     54 #include "donau-database/update_charity.h"
     55 
     56 /**
     57  * Global result from the testcase.
     58  */
     59 static int result;
     60 
     61 /**
     62  * Report line of error if @a cond is true, and jump to label "drop".
     63  */
     64 #define FAILIF(cond)                              \
     65         do {                                          \
     66           if (! (cond)) { break;}                      \
     67           GNUNET_break (0);                           \
     68           goto drop;                                  \
     69         } while (0)
     70 
     71 
     72 /**
     73  * Initializes @a ptr with random data.
     74  */
     75 #define RND_BLK(ptr)                                                    \
     76         GNUNET_CRYPTO_random_block (ptr, sizeof (* \
     77                                                  ptr))
     78 
     79 /**
     80  * Initializes @a ptr with zeros.
     81  */
     82 #define ZR_BLK(ptr) \
     83         memset (ptr, 0, sizeof (*ptr))
     84 
     85 /**
     86  * How big do we make the RSA keys?
     87  */
     88 #define RSA_KEY_SIZE 1024
     89 
     90 /**
     91  * Currency we use.  Must match test-donau-db-*.conf.
     92  */
     93 #define CURRENCY "EUR"
     94 
     95 /**
     96  * Database plugin under test.
     97  */
     98 static struct DONAUDB_PostgresContext *ctx;
     99 
    100 /**
    101  * Denomination key pair used to manufacture blinded donation unit
    102  * signatures for the regression tests below.  Created on demand.
    103  */
    104 static struct TALER_DenominationPrivateKey test_denom_priv;
    105 
    106 /**
    107  * Public key matching #test_denom_priv.
    108  */
    109 static struct TALER_DenominationPublicKey test_denom_pub;
    110 
    111 
    112 /**
    113  * Create a blinded donation unit signature usable as filler data
    114  * for `receipts_issued.blinded_sig'.
    115  *
    116  * @return freshly allocated blinded signature
    117  */
    118 static struct GNUNET_CRYPTO_BlindedSignature *
    119 make_blinded_sig (void)
    120 {
    121   struct GNUNET_CRYPTO_BlindedMessage *rp;
    122   struct GNUNET_CRYPTO_BlindedSignature *bs;
    123 
    124   if (NULL == test_denom_priv.bsign_priv_key)
    125     GNUNET_assert (GNUNET_OK ==
    126                    TALER_denom_priv_create (&test_denom_priv,
    127                                             &test_denom_pub,
    128                                             GNUNET_CRYPTO_BSA_RSA,
    129                                             RSA_KEY_SIZE));
    130   rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
    131   rp->cipher = GNUNET_CRYPTO_BSA_RSA;
    132   rp->rc = 1;
    133   rp->details.rsa_blinded_message.blinded_msg_size = 32;
    134   rp->details.rsa_blinded_message.blinded_msg = GNUNET_malloc (32);
    135   GNUNET_CRYPTO_random_block (rp->details.rsa_blinded_message.blinded_msg,
    136                               32);
    137   bs = GNUNET_CRYPTO_blind_sign (test_denom_priv.bsign_priv_key,
    138                                  "rw",
    139                                  rp);
    140   GNUNET_assert (NULL != bs);
    141   GNUNET_CRYPTO_blinded_message_decref (rp);
    142   return bs;
    143 }
    144 
    145 
    146 /**
    147  * Register a fresh charity with a random public key.
    148  *
    149  * @param max_per_year annual donation limit to use
    150  * @param[out] charity_id set to the ID of the new charity
    151  * @return #GNUNET_OK on success
    152  */
    153 static enum GNUNET_GenericReturnValue
    154 make_charity (const char *max_per_year,
    155               uint64_t *charity_id)
    156 {
    157   struct DONAU_CharityPublicKeyP charity_pub;
    158   struct TALER_Amount max;
    159 
    160   RND_BLK (&charity_pub);
    161   GNUNET_assert (GNUNET_OK ==
    162                  TALER_string_to_amount (max_per_year,
    163                                          &max));
    164   return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    165           DONAUDB_insert_charity (ctx,
    166                                   &charity_pub,
    167                                   "test charity",
    168                                   "https://charity.example.com/",
    169                                   &max,
    170                                   charity_id))
    171          ? GNUNET_OK
    172          : GNUNET_SYSERR;
    173 }
    174 
    175 
    176 /**
    177  * Issue one (fresh, never seen before) receipt for @a charity_id.
    178  *
    179  * @param charity_id charity to issue for
    180  * @param year year to attribute the receipt to
    181  * @param amount amount of the receipt
    182  * @param[out] under_limit set to true if the charity stayed below its limit
    183  * @return database status of the operation
    184  */
    185 static enum GNUNET_DB_QueryStatus
    186 issue_receipt (uint64_t charity_id,
    187                uint32_t year,
    188                const char *amount,
    189                bool *under_limit)
    190 {
    191   struct DONAU_BlindedDonationUnitSignature du_sigs[1];
    192   struct DONAU_DonationReceiptHashP h_receipt;
    193   struct TALER_Amount amt;
    194   bool charity_unknown;
    195   enum GNUNET_DB_QueryStatus qs;
    196 
    197   RND_BLK (&h_receipt);
    198   GNUNET_assert (GNUNET_OK ==
    199                  TALER_string_to_amount (amount,
    200                                          &amt));
    201   du_sigs[0].blinded_sig = make_blinded_sig ();
    202   qs = DONAUDB_do_insert_receipt_issued (ctx,
    203                                          year,
    204                                          1,
    205                                          du_sigs,
    206                                          charity_id,
    207                                          &h_receipt,
    208                                          &amt,
    209                                          under_limit,
    210                                          &charity_unknown);
    211   GNUNET_CRYPTO_blinded_sig_decref (du_sigs[0].blinded_sig);
    212   return qs;
    213 }
    214 
    215 
    216 /**
    217  * Return charities information.
    218  *
    219  * @param cls closure
    220  */
    221 static enum GNUNET_GenericReturnValue
    222 charities_cb (
    223   void *cls,
    224   uint64_t charity_id,
    225   const struct DONAU_CharityPublicKeyP *charity_pub,
    226   const char *charity_name,
    227   const struct TALER_Amount *max_per_year,
    228   uint32_t current_year,
    229   const struct TALER_Amount *receipts_to_date)
    230 {
    231   (void) cls;
    232   (void) charity_id;
    233   (void) charity_name;
    234   (void) max_per_year;
    235   (void) current_year;
    236   (void) receipts_to_date;
    237   return GNUNET_OK;
    238 }
    239 
    240 
    241 /**
    242  * Function called with information about the donau's donation_unit keys.
    243  *
    244  * @param cls NULL
    245  * @param donation_unit_pub public key of the donation_unit
    246  * @param h_donation_unit_pub hash of @a donation_unit_pub
    247  * @param validity_year of the donation unit
    248  * @param value of the donation unit
    249  */
    250 static enum GNUNET_GenericReturnValue
    251 donation_unit_info_cb (
    252   void *cls,
    253   const struct DONAU_DonationUnitHashP *h_donation_unit_pub,
    254   const struct DONAU_DonationUnitPublicKey *donation_unit_pub,
    255   uint64_t validity_year,
    256   struct TALER_Amount *value)
    257 {
    258   (void) cls;
    259   (void) h_donation_unit_pub;
    260   (void) donation_unit_pub;
    261   (void) validity_year;
    262   (void) value;
    263   return GNUNET_OK;
    264 }
    265 
    266 
    267 /**
    268  * Create an unblinded donation unit signature.  The database layer only
    269  * serializes it, so any well-formed signature will do.
    270  *
    271  * @return freshly allocated unblinded signature
    272  */
    273 static struct GNUNET_CRYPTO_UnblindedSignature *
    274 make_unblinded_sig (void)
    275 {
    276   struct GNUNET_CRYPTO_UnblindedSignature *us;
    277   struct GNUNET_HashCode msg;
    278 
    279   if (NULL == test_denom_priv.bsign_priv_key)
    280     GNUNET_assert (GNUNET_OK ==
    281                    TALER_denom_priv_create (&test_denom_priv,
    282                                             &test_denom_pub,
    283                                             GNUNET_CRYPTO_BSA_RSA,
    284                                             RSA_KEY_SIZE));
    285   RND_BLK (&msg);
    286   us = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
    287   us->cipher = GNUNET_CRYPTO_BSA_RSA;
    288   us->rc = 1;
    289   us->details.rsa_signature
    290     = GNUNET_CRYPTO_rsa_sign_fdh (
    291         test_denom_priv.bsign_priv_key->details.rsa_private_key,
    292         &msg,
    293         sizeof (msg));
    294   GNUNET_assert (NULL != us->details.rsa_signature);
    295   return us;
    296 }
    297 
    298 
    299 /**
    300  * Register a donation unit with a random key hash.
    301  *
    302  * @param[out] h_donation_unit_pub set to the hash identifying the unit
    303  * @param year validity year of the unit
    304  * @param value value of the unit
    305  * @return database status of the operation
    306  */
    307 static enum GNUNET_DB_QueryStatus
    308 make_donation_unit (struct DONAU_DonationUnitHashP *h_donation_unit_pub,
    309                     uint64_t year,
    310                     const char *value)
    311 {
    312   struct TALER_DenominationPrivateKey dpriv;
    313   struct TALER_DenominationPublicKey dpub;
    314   struct DONAU_DonationUnitPublicKey du_pub;
    315   struct TALER_Amount val;
    316   enum GNUNET_DB_QueryStatus qs;
    317 
    318   RND_BLK (h_donation_unit_pub);
    319   GNUNET_assert (GNUNET_OK ==
    320                  TALER_denom_priv_create (&dpriv,
    321                                           &dpub,
    322                                           GNUNET_CRYPTO_BSA_RSA,
    323                                           RSA_KEY_SIZE));
    324   du_pub.bsign_pub_key = dpub.bsign_pub_key;
    325   GNUNET_assert (GNUNET_OK ==
    326                  TALER_string_to_amount (value,
    327                                          &val));
    328   qs = DONAUDB_insert_donation_unit (ctx,
    329                                      h_donation_unit_pub,
    330                                      &du_pub,
    331                                      year,
    332                                      &val);
    333   TALER_denom_priv_free (&dpriv);
    334   TALER_denom_pub_free (&dpub);
    335   return qs;
    336 }
    337 
    338 
    339 /**
    340  * Function called with information about the donau's online signing keys.
    341  *
    342  * @param cls NULL
    343  * @param donau_pub the public key
    344  * @param meta meta data information about the denomination type (expirations)
    345  */
    346 static void
    347 iterate_active_signkeys_cb (
    348   void *cls,
    349   const struct DONAU_DonauPublicKeyP *donau_pub,
    350   struct DONAUDB_SignkeyMetaData *meta)
    351 {
    352   (void) cls;
    353   (void) donau_pub;
    354   (void) meta;
    355 }
    356 
    357 
    358 /**
    359  * Closure for #check_charity_cb().
    360  */
    361 struct CharityCheck
    362 {
    363   /**
    364    * Charity we are interested in.
    365    */
    366   uint64_t charity_id;
    367 
    368   /**
    369    * Value #DONAUDB_iterate_charities() reported for it.
    370    */
    371   struct TALER_Amount receipts_to_date;
    372 
    373   /**
    374    * Set to true if the charity was seen.
    375    */
    376   bool found;
    377 };
    378 
    379 
    380 /**
    381  * Capture what #DONAUDB_iterate_charities() reports for one charity.
    382  *
    383  * @param cls a `struct CharityCheck *`
    384  * @param charity_id charity of this row
    385  * @param charity_pub public key of the charity
    386  * @param charity_name name of the charity
    387  * @param max_per_year annual limit
    388  * @param current_year year `receipts_to_date' belongs to
    389  * @param receipts_to_date total booked so far
    390  * @return #GNUNET_OK to continue
    391  */
    392 static enum GNUNET_GenericReturnValue
    393 check_charity_cb (void *cls,
    394                   uint64_t charity_id,
    395                   const struct DONAU_CharityPublicKeyP *charity_pub,
    396                   const char *charity_name,
    397                   const struct TALER_Amount *max_per_year,
    398                   uint32_t current_year,
    399                   const struct TALER_Amount *receipts_to_date)
    400 {
    401   struct CharityCheck *cc = cls;
    402 
    403   (void) charity_pub;
    404   (void) charity_name;
    405   (void) max_per_year;
    406   (void) current_year;
    407   if (charity_id != cc->charity_id)
    408     return GNUNET_OK;
    409   cc->receipts_to_date = *receipts_to_date;
    410   cc->found = true;
    411   return GNUNET_OK;
    412 }
    413 
    414 
    415 /**
    416  * Closure for #collect_history_cb().
    417  */
    418 struct HistoryCheck
    419 {
    420   /**
    421    * Charity we are interested in.
    422    */
    423   unsigned long long charity_id;
    424 
    425   /**
    426    * Year we expect a history entry for.
    427    */
    428   uint64_t donation_year;
    429 
    430   /**
    431    * Amount we expect that entry to carry.
    432    */
    433   struct TALER_Amount expected;
    434 
    435   /**
    436    * Set to true if the expected entry was found.
    437    */
    438   bool found;
    439 };
    440 
    441 
    442 /**
    443  * Look for the history entry described by @a cls.
    444  *
    445  * @param cls a `struct HistoryCheck *`
    446  * @param charity_id charity of this entry
    447  * @param final_amount closing total of that year
    448  * @param donation_year year of this entry
    449  * @return #GNUNET_OK to continue
    450  */
    451 static enum GNUNET_GenericReturnValue
    452 collect_history_cb (void *cls,
    453                     unsigned long long charity_id,
    454                     struct TALER_Amount final_amount,
    455                     uint64_t donation_year)
    456 {
    457   struct HistoryCheck *hc = cls;
    458 
    459   if ( (charity_id == hc->charity_id) &&
    460        (donation_year == hc->donation_year) &&
    461        (0 == TALER_amount_cmp (&final_amount,
    462                                &hc->expected)) )
    463     hc->found = true;
    464   return GNUNET_OK;
    465 }
    466 
    467 
    468 /**
    469  * Main function that will be run by the scheduler.
    470  *
    471  * @param cls closure with config
    472  */
    473 static void
    474 run (void *cls)
    475 {
    476   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    477   unsigned int year;
    478 
    479   // Charity information
    480   json_t *charities;
    481   struct DONAU_CharityPublicKeyP charity_pub;
    482   struct DONAUDB_CharityMetaData charity_meta;
    483   const char *charity_name;
    484   const char *charity_url;
    485   struct TALER_Amount max_per_year;
    486   struct TALER_Amount receipts_to_date;
    487   uint64_t charity_id;
    488 
    489   // Donation unit information
    490   struct DONAU_DonationUnitHashP h_donation_unit_pub;
    491   uint64_t validity_year;
    492   struct TALER_Amount du_value;
    493 
    494   // Signing key information
    495   struct DONAU_DonauPublicKeyP donau_pub;
    496   struct DONAUDB_SignkeyMetaData sk_meta;
    497 
    498   // Issued receipts information
    499   size_t num_b_sigs = 1;
    500   struct DONAU_BlindedDonationUnitSignature du_sigs[num_b_sigs];
    501   struct DONAU_DonationReceiptHashP h_receipt;
    502   struct TALER_Amount amount_receipts;
    503   bool smaller_than_max_per_year;
    504   bool charity_unknown;
    505   bool charity_in_use;
    506   struct TALER_DenominationPrivateKey denom_priv;
    507   struct TALER_DenominationPublicKey denom_pub;
    508   struct DONAU_DonationUnitPublicKey du_pub;
    509   struct GNUNET_CRYPTO_BlindedMessage *rp;
    510   struct GNUNET_CRYPTO_RsaBlindedMessage *rsa;
    511 
    512   if (NULL ==
    513       (ctx = DONAUDB_connect_admin (cfg)))
    514   {
    515     fprintf (stderr,
    516              "Failed to connect to database\n");
    517     result = 77;
    518     return;
    519   }
    520   (void) DONAUDB_drop_tables (ctx);
    521   if (GNUNET_OK !=
    522       DONAUDB_create_tables (ctx))
    523   {
    524     fprintf (stderr,
    525              "Failed to create DB tables\n");
    526     result = 77;
    527     goto cleanup;
    528   }
    529   DONAUDB_preflight (ctx);
    530   FAILIF (GNUNET_OK !=
    531           DONAUDB_start (ctx,
    532                          "test-1"));
    533 
    534   fprintf (stderr,
    535            "Running DB tests\n");
    536 
    537   /* test DB is empty */
    538   charity_id = 1;
    539   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    540           DONAUDB_get_charity (ctx,
    541                                charity_id,
    542                                &charity_meta));
    543 
    544   /* test insert charity */
    545   charity_name = "charity_name";
    546   charity_url = "charity_url";
    547   charities = json_array ();
    548   RND_BLK (&charity_pub);
    549   GNUNET_assert (GNUNET_OK ==
    550                  TALER_string_to_amount (CURRENCY ":1.000010",
    551                                          &max_per_year));
    552   GNUNET_assert (GNUNET_OK ==
    553                  TALER_string_to_amount (CURRENCY ":0.000010",
    554                                          &receipts_to_date));
    555 
    556   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    557           DONAUDB_insert_charity (ctx,
    558                                   &charity_pub,
    559                                   charity_name,
    560                                   charity_url,
    561                                   &max_per_year,
    562                                   &charity_id));
    563 
    564   /* test get charities */
    565   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    566           DONAUDB_iterate_charities (ctx,
    567                                      &charities_cb,
    568                                      charities));
    569 
    570   {
    571     /* Update the charity and verify the new key and metadata persist. */
    572     const char *updated_charity_name = "charity_name_updated";
    573     const char *updated_charity_url = "charity_url_updated";
    574     struct TALER_Amount updated_max;
    575     struct DONAU_CharityPrivateKeyP updated_charity_priv;
    576     struct DONAU_CharityPublicKeyP updated_charity_pub;
    577     bool conflict = true;
    578 
    579     GNUNET_CRYPTO_eddsa_key_create (&updated_charity_priv.eddsa_priv);
    580     GNUNET_CRYPTO_eddsa_key_get_public (&updated_charity_priv.eddsa_priv,
    581                                         &updated_charity_pub.eddsa_pub);
    582 
    583     GNUNET_assert (GNUNET_OK ==
    584                    TALER_string_to_amount (CURRENCY ":2.000000",
    585                                            &updated_max));
    586     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    587             DONAUDB_update_charity (ctx,
    588                                     charity_id,
    589                                     &updated_charity_pub,
    590                                     updated_charity_name,
    591                                     updated_charity_url,
    592                                     &updated_max,
    593                                     &conflict));
    594     FAILIF (conflict);
    595 
    596     ZR_BLK (&charity_meta);
    597     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    598             DONAUDB_get_charity (ctx,
    599                                  charity_id,
    600                                  &charity_meta));
    601     GNUNET_assert (0 == GNUNET_memcmp (&charity_meta.charity_pub,
    602                                        &updated_charity_pub));
    603     GNUNET_assert (0 == strcmp (charity_meta.charity_name,
    604                                 updated_charity_name));
    605     GNUNET_assert (0 == strcmp (charity_meta.charity_url,
    606                                 updated_charity_url));
    607     GNUNET_assert (0 == TALER_amount_cmp (&charity_meta.max_per_year,
    608                                           &updated_max));
    609     GNUNET_free (charity_meta.charity_name);
    610     GNUNET_free (charity_meta.charity_url);
    611 
    612     charity_name = updated_charity_name;
    613     charity_url = updated_charity_url;
    614     max_per_year = updated_max;
    615     charity_pub = updated_charity_pub;
    616   }
    617 
    618   /* test delete charity */
    619   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    620           DONAUDB_delete_charity (ctx,
    621                                   charity_id,
    622                                   &charity_in_use));
    623 
    624   /* test insert donation unit */
    625   RND_BLK (&h_donation_unit_pub);
    626   GNUNET_assert (GNUNET_OK ==
    627                  TALER_denom_priv_create (&denom_priv,
    628                                           &denom_pub,
    629                                           GNUNET_CRYPTO_BSA_RSA,
    630                                           RSA_KEY_SIZE));
    631   du_pub.bsign_pub_key = denom_pub.bsign_pub_key;
    632   validity_year = 2024;
    633   GNUNET_assert (GNUNET_OK ==
    634                  TALER_string_to_amount (CURRENCY ":1.000010",
    635                                          &du_value));
    636 
    637   /* test iterate donation units */
    638   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    639           DONAUDB_iterate_donation_units (ctx,
    640                                           &donation_unit_info_cb,
    641                                           NULL));
    642 
    643   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    644           DONAUDB_insert_donation_unit (ctx,
    645                                         &h_donation_unit_pub,
    646                                         &du_pub,
    647                                         validity_year,
    648                                         &du_value));
    649 
    650   /* test iterate donation units */
    651   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    652           DONAUDB_iterate_donation_units (ctx,
    653                                           &donation_unit_info_cb,
    654                                           NULL));
    655 
    656   /* D-9: the security module re-announces every donation unit on each
    657      restart, and h_donation_unit_pub covers the key material only.  A
    658      donation_units row is a historical fact -- receipts already issued under
    659      this key are summed from its value -- so a re-announce with a different
    660      year and value must leave the stored row untouched. */
    661   {
    662     struct TALER_Amount other_value;
    663     struct TALER_Amount stored_value;
    664 
    665     GNUNET_assert (GNUNET_OK ==
    666                    TALER_string_to_amount (CURRENCY ":2.000020",
    667                                            &other_value));
    668     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    669             DONAUDB_insert_donation_unit (ctx,
    670                                           &h_donation_unit_pub,
    671                                           &du_pub,
    672                                           validity_year + 1,
    673                                           &other_value));
    674     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    675             DONAUDB_get_donation_unit_amount (ctx,
    676                                               &h_donation_unit_pub,
    677                                               &stored_value));
    678     FAILIF (0 != TALER_amount_cmp (&stored_value,
    679                                    &du_value));
    680   }
    681 
    682   /* D-9: a key the operator retires is replaced by a *new* key, which may
    683      well carry the same year and value.  Both must be storable side by side,
    684      so that the signatures already made with the old one stay accountable. */
    685   {
    686     struct TALER_DenominationPrivateKey second_priv;
    687     struct TALER_DenominationPublicKey second_pub;
    688     struct DONAU_DonationUnitPublicKey second_du_pub;
    689     struct DONAU_DonationUnitHashP second_h_du_pub;
    690 
    691     RND_BLK (&second_h_du_pub);
    692     GNUNET_assert (GNUNET_OK ==
    693                    TALER_denom_priv_create (&second_priv,
    694                                             &second_pub,
    695                                             GNUNET_CRYPTO_BSA_RSA,
    696                                             RSA_KEY_SIZE));
    697     second_du_pub.bsign_pub_key = second_pub.bsign_pub_key;
    698     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    699             DONAUDB_insert_donation_unit (ctx,
    700                                           &second_h_du_pub,
    701                                           &second_du_pub,
    702                                           validity_year,
    703                                           &du_value));
    704     TALER_denom_priv_free (&second_priv);
    705     TALER_denom_pub_free (&second_pub);
    706   }
    707 
    708   TALER_denom_pub_free (&denom_pub);
    709 
    710   /* test insert signing key */
    711   RND_BLK (&donau_pub);
    712   year = GNUNET_TIME_get_current_year ();
    713   sk_meta.expire_legal = GNUNET_TIME_absolute_to_timestamp (
    714     GNUNET_TIME_year_to_time (year + 5));
    715   sk_meta.expire_sign = GNUNET_TIME_absolute_to_timestamp (
    716     GNUNET_TIME_year_to_time (year + 1));
    717   sk_meta.valid_from = GNUNET_TIME_absolute_to_timestamp (
    718     GNUNET_TIME_year_to_time (year));
    719 
    720 
    721   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    722           DONAUDB_insert_signkey (ctx,
    723                                   &donau_pub,
    724                                   &sk_meta));
    725 
    726   /* test iterate signing key */
    727   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    728           DONAUDB_iterate_active_signkeys (ctx,
    729                                            &iterate_active_signkeys_cb,
    730                                            NULL));
    731 
    732   /* test insert issued receipt */
    733   rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
    734   rp->cipher = GNUNET_CRYPTO_BSA_RSA;
    735   rp->rc = 1;
    736   rsa = &rp->details.rsa_blinded_message;
    737   rsa->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
    738     (RSA_KEY_SIZE / 8) - 1);
    739   rsa->blinded_msg = GNUNET_malloc (rsa->blinded_msg_size);
    740   GNUNET_CRYPTO_random_block (rsa->blinded_msg,
    741                               rsa->blinded_msg_size);
    742   smaller_than_max_per_year = false;
    743   GNUNET_assert (GNUNET_OK ==
    744                  TALER_string_to_amount (CURRENCY ":1.000010",
    745                                          &amount_receipts));
    746 
    747   du_sigs[0].blinded_sig =
    748     GNUNET_CRYPTO_blind_sign (denom_priv.bsign_priv_key,
    749                               "rw",
    750                               rp);
    751 
    752   GNUNET_assert (NULL != du_sigs[0].blinded_sig);
    753   TALER_denom_priv_free (&denom_priv);
    754 
    755   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    756           DONAUDB_do_insert_receipt_issued (ctx,
    757                                             GNUNET_TIME_get_current_year (),
    758                                             num_b_sigs,
    759                                             du_sigs,
    760                                             charity_id,
    761                                             &h_receipt,
    762                                             &amount_receipts,
    763                                             &smaller_than_max_per_year,
    764                                             &charity_unknown));
    765 
    766   // FIXME
    767   // FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    768   //         DONAUDB_get_receipt_issued (ctx,
    769   //                                         &h_receipt,
    770   //                                         &ir_meta));
    771 
    772   /* test insert submitted receipts */
    773   // RND_BLK (&h_donor_tax_id);
    774   // RND_BLK (&donation_receipts[0].h_donation_unit_pub);
    775   // RND_BLK (&donation_receipts[0].nonce);
    776   // RND_BLK (&donation_receipts[0].donation_unit_sig);
    777   // FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    778   //         DONAUDB_insert_receipts_submitted (ctx,
    779   //                                            &h_donor_tax_id,
    780   //                                            num_dr,
    781   //                                            donation_receipts,
    782   //                                            current_year));
    783 
    784   DONAUDB_preflight (ctx);
    785 
    786   /* A transaction PostgreSQL has already aborted must never be reported as
    787      committed -- and a clean transaction must still return exactly
    788      GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which is what every caller that
    789      compares against 0 relies on. */
    790   {
    791     struct GNUNET_PQ_ExecuteStatement mk[] = {
    792       GNUNET_PQ_make_execute ("CREATE TABLE test_commit_rollback (x INT)"),
    793       GNUNET_PQ_EXECUTE_STATEMENT_END
    794     };
    795     struct GNUNET_PQ_ExecuteStatement bad[] = {
    796       GNUNET_PQ_make_execute ("SELECT 1/0"),
    797       GNUNET_PQ_EXECUTE_STATEMENT_END
    798     };
    799     struct GNUNET_PQ_ExecuteStatement chk[] = {
    800       GNUNET_PQ_make_execute (
    801         "DO $$ BEGIN"
    802         " IF EXISTS (SELECT FROM pg_class"
    803         "             WHERE relname='test_commit_rollback')"
    804         " THEN RAISE EXCEPTION 'table survived a rollback';"
    805         " END IF; END $$"),
    806       GNUNET_PQ_EXECUTE_STATEMENT_END
    807     };
    808 
    809     FAILIF (GNUNET_OK !=
    810             DONAUDB_start (ctx,
    811                            "test-commit-aborted"));
    812     FAILIF (GNUNET_OK !=
    813             GNUNET_PQ_exec_statements (ctx->conn,
    814                                        mk));
    815     /* provoke an error; from here on the transaction is doomed */
    816     FAILIF (GNUNET_OK ==
    817             GNUNET_PQ_exec_statements (ctx->conn,
    818                                        bad));
    819     /* before the fix this returned GNUNET_DB_STATUS_SUCCESS_NO_RESULTS (0) */
    820     FAILIF (0 <=
    821             DONAUDB_commit (ctx));
    822     /* ...and the table is indeed gone, so 'success' would have been a lie */
    823     FAILIF (GNUNET_OK !=
    824             GNUNET_PQ_exec_statements (ctx->conn,
    825                                        chk));
    826 
    827     FAILIF (GNUNET_OK !=
    828             DONAUDB_start (ctx,
    829                            "test-commit-clean"));
    830     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    831             DONAUDB_commit (ctx));
    832   }
    833 
    834   /* D-5: `history' is documented as holding the *yearly* total per
    835      charity, so it must be able to hold more than one year per charity,
    836      and a repeated write for the same year must be distinguishable from
    837      a successful one. */
    838   {
    839     uint64_t hist_charity_id;
    840     struct TALER_Amount y1;
    841     struct TALER_Amount y2;
    842 
    843     FAILIF (GNUNET_OK !=
    844             make_charity (CURRENCY ":1000",
    845                           &hist_charity_id));
    846     GNUNET_assert (GNUNET_OK ==
    847                    TALER_string_to_amount (CURRENCY ":90",
    848                                            &y1));
    849     GNUNET_assert (GNUNET_OK ==
    850                    TALER_string_to_amount (CURRENCY ":75",
    851                                            &y2));
    852     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    853             DONAUDB_insert_history_entry (ctx,
    854                                           hist_charity_id,
    855                                           &y1,
    856                                           2026));
    857     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    858             DONAUDB_insert_history_entry (ctx,
    859                                           hist_charity_id,
    860                                           &y2,
    861                                           2027));
    862     /* Re-recording the same year must report "nothing written". */
    863     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    864             DONAUDB_insert_history_entry (ctx,
    865                                           hist_charity_id,
    866                                           &y2,
    867                                           2027));
    868   }
    869 
    870   /* D-4: the first request of a new year resets receipts_to_date; the
    871      closing total of the year being left must be preserved in `history'
    872      rather than discarded. */
    873   {
    874     uint64_t rollover_charity_id;
    875     struct DONAUDB_CharityMetaData rollover_meta;
    876     struct HistoryCheck hc;
    877     struct TALER_Amount zero;
    878     bool under_limit = false;
    879     uint32_t this_year = GNUNET_TIME_get_current_year ();
    880 
    881     FAILIF (GNUNET_OK !=
    882             make_charity (CURRENCY ":1000",
    883                           &rollover_charity_id));
    884     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    885             issue_receipt (rollover_charity_id,
    886                            this_year,
    887                            CURRENCY ":90",
    888                            &under_limit));
    889     FAILIF (! under_limit);
    890     /* Pretend those 90 were booked last year: that is the state every
    891        charity is in on the 1st of January, before its first request. */
    892     {
    893       struct GNUNET_PQ_QueryParam uparams[] = {
    894         GNUNET_PQ_query_param_uint64 (&rollover_charity_id),
    895         GNUNET_PQ_query_param_end
    896       };
    897 
    898       FAILIF (GNUNET_OK !=
    899               GNUNET_PQ_prepare_anon (ctx->conn,
    900                                       "UPDATE charities"
    901                                       "   SET current_year=current_year-1"
    902                                       " WHERE charity_id=$1;"));
    903       FAILIF (0 >=
    904               GNUNET_PQ_eval_prepared_non_select (ctx->conn,
    905                                                   "",
    906                                                   uparams));
    907     }
    908     /* First request of the new year. */
    909     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    910             issue_receipt (rollover_charity_id,
    911                            this_year,
    912                            CURRENCY ":5",
    913                            &under_limit));
    914     FAILIF (! under_limit);
    915 
    916     memset (&hc,
    917             0,
    918             sizeof (hc));
    919     hc.charity_id = (unsigned long long) rollover_charity_id;
    920     hc.donation_year = this_year - 1;
    921     GNUNET_assert (GNUNET_OK ==
    922                    TALER_string_to_amount (CURRENCY ":90",
    923                                            &hc.expected));
    924     FAILIF (0 >
    925             DONAUDB_iterate_history_entries (ctx,
    926                                              &collect_history_cb,
    927                                              &hc));
    928     FAILIF (! hc.found);
    929 
    930     /* ... and the new year did start from zero. */
    931     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    932             DONAUDB_get_charity (ctx,
    933                                  rollover_charity_id,
    934                                  &rollover_meta));
    935     GNUNET_assert (GNUNET_OK ==
    936                    TALER_string_to_amount (CURRENCY ":5",
    937                                            &zero));
    938     FAILIF (0 != TALER_amount_cmp (&rollover_meta.receipts_to_date,
    939                                    &zero));
    940     GNUNET_free (rollover_meta.charity_name);
    941     GNUNET_free (rollover_meta.charity_url);
    942   }
    943 
    944   /* D-6: updating a charity to a charity_pub that another charity
    945      already owns must be reported as a conflict, not as "no such
    946      charity" -- and it must not abort the caller's transaction. */
    947   {
    948     uint64_t first_id;
    949     uint64_t second_id;
    950     struct DONAUDB_CharityMetaData first_meta;
    951     struct DONAUDB_CharityMetaData second_meta;
    952     struct TALER_Amount some_max;
    953     bool conflict = false;
    954 
    955     FAILIF (GNUNET_OK !=
    956             make_charity (CURRENCY ":100",
    957                           &first_id));
    958     FAILIF (GNUNET_OK !=
    959             make_charity (CURRENCY ":100",
    960                           &second_id));
    961     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    962             DONAUDB_get_charity (ctx,
    963                                  first_id,
    964                                  &first_meta));
    965     GNUNET_assert (GNUNET_OK ==
    966                    TALER_string_to_amount (CURRENCY ":100",
    967                                            &some_max));
    968     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    969             DONAUDB_update_charity (ctx,
    970                                     second_id,
    971                                     &first_meta.charity_pub,
    972                                     "stolen",
    973                                     "https://stolen.example.com/",
    974                                     &some_max,
    975                                     &conflict));
    976     FAILIF (! conflict);
    977     /* The second charity must be untouched ... */
    978     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    979             DONAUDB_get_charity (ctx,
    980                                  second_id,
    981                                  &second_meta));
    982     FAILIF (0 == GNUNET_memcmp (&second_meta.charity_pub,
    983                                 &first_meta.charity_pub));
    984     /* ... and an unknown charity_id must still be "no results". */
    985     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    986             DONAUDB_update_charity (ctx,
    987                                     second_id + 424242,
    988                                     &second_meta.charity_pub,
    989                                     "ghost",
    990                                     "https://ghost.example.com/",
    991                                     &some_max,
    992                                     &conflict));
    993     FAILIF (conflict);
    994     GNUNET_free (first_meta.charity_name);
    995     GNUNET_free (first_meta.charity_url);
    996     GNUNET_free (second_meta.charity_name);
    997     GNUNET_free (second_meta.charity_url);
    998   }
    999 
   1000   /* D-7: `receipts_submitted.nonce' is globally unique, so a donor who
   1001      reuses one UDI nonce for a *different* donation unit in a second
   1002      request has that receipt silently dropped.  The DB layer must tell
   1003      the caller which index was refused, so /batch-submit can answer 409
   1004      instead of 201. */
   1005   {
   1006     struct DONAU_HashDonorTaxId donor;
   1007     struct DONAU_DonationUnitHashP du_a;
   1008     struct DONAU_DonationUnitHashP du_b;
   1009     struct DONAU_DonationReceipt dr;
   1010     struct DONAU_UniqueDonorIdentifierNonce nonce;
   1011     struct TALER_Amount total;
   1012     struct TALER_Amount expected;
   1013     size_t conflict_index = 42;
   1014     size_t unknown_index = 42;
   1015     uint64_t this_year = GNUNET_TIME_get_current_year ();
   1016 
   1017     RND_BLK (&donor);
   1018     RND_BLK (&nonce);
   1019     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1020             make_donation_unit (&du_a,
   1021                                 this_year,
   1022                                 CURRENCY ":10"));
   1023     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1024             make_donation_unit (&du_b,
   1025                                 this_year,
   1026                                 CURRENCY ":25"));
   1027 
   1028     dr.h_donation_unit_pub = du_a;
   1029     dr.nonce = nonce;
   1030     dr.donation_unit_sig.unblinded_sig = make_unblinded_sig ();
   1031     FAILIF (0 >
   1032             DONAUDB_insert_receipts_submitted (ctx,
   1033                                                &donor,
   1034                                                1,
   1035                                                &dr,
   1036                                                this_year,
   1037                                                &conflict_index,
   1038                                                &unknown_index));
   1039     FAILIF (1 != conflict_index);   /* == num_dr, i.e. no conflict */
   1040     GNUNET_CRYPTO_unblinded_sig_decref (dr.donation_unit_sig.unblinded_sig);
   1041 
   1042     /* Same donor, same nonce, different donation unit: refused. */
   1043     dr.h_donation_unit_pub = du_b;
   1044     dr.nonce = nonce;
   1045     dr.donation_unit_sig.unblinded_sig = make_unblinded_sig ();
   1046     FAILIF (0 >
   1047             DONAUDB_insert_receipts_submitted (ctx,
   1048                                                &donor,
   1049                                                1,
   1050                                                &dr,
   1051                                                this_year,
   1052                                                &conflict_index,
   1053                                                &unknown_index));
   1054     FAILIF (0 != conflict_index);
   1055     GNUNET_CRYPTO_unblinded_sig_decref (dr.donation_unit_sig.unblinded_sig);
   1056 
   1057     /* Only the first receipt is deductible; the second one was dropped. */
   1058     FAILIF (0 >
   1059             DONAUDB_get_receipts_submitted_total (ctx,
   1060                                                   this_year,
   1061                                                   &donor,
   1062                                                   &total));
   1063     GNUNET_assert (GNUNET_OK ==
   1064                    TALER_string_to_amount (CURRENCY ":10",
   1065                                            &expected));
   1066     FAILIF (0 != TALER_amount_cmp (&total,
   1067                                    &expected));
   1068   }
   1069 
   1070   /* D-10: h_donation_unit_pub is client-supplied and is a foreign key.
   1071      ON CONFLICT DO NOTHING does not absorb foreign key violations, so an
   1072      unknown unit used to escape as a hard error (HTTP 500).  It must be
   1073      reported as "unknown donation unit at index i", and nothing at all
   1074      may be written for that batch. */
   1075   {
   1076     struct DONAU_HashDonorTaxId donor;
   1077     struct DONAU_DonationUnitHashP known_du;
   1078     struct DONAU_DonationReceipt drs[2];
   1079     struct TALER_Amount total;
   1080     struct TALER_Amount zero;
   1081     size_t conflict_index = 42;
   1082     size_t unknown_index = 42;
   1083     uint64_t this_year = GNUNET_TIME_get_current_year ();
   1084 
   1085     RND_BLK (&donor);
   1086     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1087             make_donation_unit (&known_du,
   1088                                 this_year,
   1089                                 CURRENCY ":10"));
   1090     drs[0].h_donation_unit_pub = known_du;
   1091     RND_BLK (&drs[0].nonce);
   1092     drs[0].donation_unit_sig.unblinded_sig = make_unblinded_sig ();
   1093     /* Second receipt names a donation unit that was never registered. */
   1094     RND_BLK (&drs[1].h_donation_unit_pub);
   1095     RND_BLK (&drs[1].nonce);
   1096     drs[1].donation_unit_sig.unblinded_sig = make_unblinded_sig ();
   1097 
   1098     FAILIF (0 >
   1099             DONAUDB_insert_receipts_submitted (ctx,
   1100                                                &donor,
   1101                                                2,
   1102                                                drs,
   1103                                                this_year,
   1104                                                &conflict_index,
   1105                                                &unknown_index));
   1106     FAILIF (1 != unknown_index);
   1107     GNUNET_CRYPTO_unblinded_sig_decref (drs[0].donation_unit_sig.unblinded_sig);
   1108     GNUNET_CRYPTO_unblinded_sig_decref (drs[1].donation_unit_sig.unblinded_sig);
   1109 
   1110     /* The valid receipt in the same batch must NOT have been stored. */
   1111     FAILIF (0 >
   1112             DONAUDB_get_receipts_submitted_total (ctx,
   1113                                                   this_year,
   1114                                                   &donor,
   1115                                                   &total));
   1116     GNUNET_assert (GNUNET_OK ==
   1117                    TALER_amount_set_zero (CURRENCY,
   1118                                           &zero));
   1119     FAILIF (0 != TALER_amount_cmp (&total,
   1120                                    &zero));
   1121   }
   1122 
   1123   /* Everything below runs in autocommit, which is what donau-httpd does. */
   1124 
   1125   /* D-1: amount_add()'s overflow guard must trigger at 2^52, not at
   1126      2^20.  A charity with a realistic annual limit must be able to
   1127      book receipts well beyond 1048576 units of currency. */
   1128   {
   1129     uint64_t big_charity_id;
   1130     bool under_limit = false;
   1131 
   1132     FAILIF (GNUNET_OK !=
   1133             make_charity (CURRENCY ":4000000000",
   1134                           &big_charity_id));
   1135     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1136             issue_receipt (big_charity_id,
   1137                            GNUNET_TIME_get_current_year (),
   1138                            CURRENCY ":2000000",
   1139                            &under_limit));
   1140     FAILIF (! under_limit);
   1141   }
   1142 
   1143   /* D-8: receipts_issued.receipt_id must not be settable from outside.
   1144      If it is, an operator bulk load or a partial restore can leave the
   1145      identity sequence behind the largest stored value, after which every
   1146      insert fails on receipts_issued_receipt_id_key -- which donau-httpd
   1147      used to answer with an assertion failure, i.e. abort(). */
   1148   {
   1149     struct GNUNET_PQ_QueryParam params[] = {
   1150       GNUNET_PQ_query_param_end
   1151     };
   1152 
   1153     /* Control: the very same insert without receipt_id is accepted. */
   1154     FAILIF (GNUNET_OK !=
   1155             GNUNET_PQ_prepare_anon (
   1156               ctx->conn,
   1157               "INSERT INTO receipts_issued"
   1158               " (blinded_sig,charity_id,receipt_hash,amount)"
   1159               " SELECT r.blinded_sig, r.charity_id,"
   1160               "        sha512(r.receipt_hash), r.amount"
   1161               "   FROM receipts_issued r"
   1162               "  LIMIT 1;"));
   1163     /* Supplying receipt_id explicitly must be refused.  With GENERATED
   1164        ALWAYS PostgreSQL already rejects it at PREPARE time; if a server
   1165        accepts the statement, executing it must fail. */
   1166     if (GNUNET_OK ==
   1167         GNUNET_PQ_prepare_anon (
   1168           ctx->conn,
   1169           "INSERT INTO receipts_issued"
   1170           " (receipt_id,blinded_sig,charity_id,receipt_hash,amount)"
   1171           " SELECT 424242, r.blinded_sig, r.charity_id,"
   1172           "        sha512(r.receipt_hash), r.amount"
   1173           "   FROM receipts_issued r"
   1174           "  LIMIT 1;"))
   1175       FAILIF (0 <=
   1176               GNUNET_PQ_eval_prepared_non_select (ctx->conn,
   1177                                                   "",
   1178                                                   params));
   1179   }
   1180 
   1181   /* D-12: between the 1st of January and a charity's first request of the
   1182      year, `receipts_to_date' still holds last year's total.
   1183      DONAUDB_iterate_charities() compensates for that; DONAUDB_get_charity()
   1184      must do the same, otherwise GET /charity/$ID and GET /charities
   1185      disagree and PATCH refuses to lower max_per_year below *last* year's
   1186      total. */
   1187   {
   1188     uint64_t stale_charity_id;
   1189     struct DONAUDB_CharityMetaData stale_meta;
   1190     struct CharityCheck cc;
   1191     struct TALER_Amount zero;
   1192     bool under_limit = false;
   1193 
   1194     FAILIF (GNUNET_OK !=
   1195             make_charity (CURRENCY ":1000",
   1196                           &stale_charity_id));
   1197     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1198             issue_receipt (stale_charity_id,
   1199                            GNUNET_TIME_get_current_year (),
   1200                            CURRENCY ":90",
   1201                            &under_limit));
   1202     FAILIF (! under_limit);
   1203     {
   1204       struct GNUNET_PQ_QueryParam uparams[] = {
   1205         GNUNET_PQ_query_param_uint64 (&stale_charity_id),
   1206         GNUNET_PQ_query_param_end
   1207       };
   1208 
   1209       FAILIF (GNUNET_OK !=
   1210               GNUNET_PQ_prepare_anon (ctx->conn,
   1211                                       "UPDATE charities"
   1212                                       "   SET current_year=current_year-1"
   1213                                       " WHERE charity_id=$1;"));
   1214       FAILIF (0 >=
   1215               GNUNET_PQ_eval_prepared_non_select (ctx->conn,
   1216                                                   "",
   1217                                                   uparams));
   1218     }
   1219     GNUNET_assert (GNUNET_OK ==
   1220                    TALER_amount_set_zero (CURRENCY,
   1221                                           &zero));
   1222     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1223             DONAUDB_get_charity (ctx,
   1224                                  stale_charity_id,
   1225                                  &stale_meta));
   1226     FAILIF (0 != TALER_amount_cmp (&stale_meta.receipts_to_date,
   1227                                    &zero));
   1228     GNUNET_free (stale_meta.charity_name);
   1229     GNUNET_free (stale_meta.charity_url);
   1230 
   1231     /* ... and the two views agree. */
   1232     memset (&cc,
   1233             0,
   1234             sizeof (cc));
   1235     cc.charity_id = stale_charity_id;
   1236     FAILIF (0 >
   1237             DONAUDB_iterate_charities (ctx,
   1238                                        &check_charity_cb,
   1239                                        &cc));
   1240     FAILIF (! cc.found);
   1241     FAILIF (0 != TALER_amount_cmp (&cc.receipts_to_date,
   1242                                    &zero));
   1243   }
   1244 
   1245   /* D-13: receipts_issued.receipt_hash is the idempotence key of
   1246      POST /batch-issue.  Deleting a charity used to cascade those rows
   1247      away, so re-registering the charity re-enabled replay of every
   1248      request it had ever made.  The delete must now be refused, and
   1249      reported as such rather than as a hard error. */
   1250   {
   1251     uint64_t busy_charity_id;
   1252     struct DONAUDB_CharityMetaData survivor;
   1253     bool under_limit = false;
   1254     bool in_use = false;
   1255 
   1256     FAILIF (GNUNET_OK !=
   1257             make_charity (CURRENCY ":1000",
   1258                           &busy_charity_id));
   1259     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1260             issue_receipt (busy_charity_id,
   1261                            GNUNET_TIME_get_current_year (),
   1262                            CURRENCY ":7",
   1263                            &under_limit));
   1264     FAILIF (! under_limit);
   1265     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1266             DONAUDB_delete_charity (ctx,
   1267                                     busy_charity_id,
   1268                                     &in_use));
   1269     FAILIF (! in_use);
   1270     /* The charity -- and with it the issued-receipt record -- survives. */
   1271     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1272             DONAUDB_get_charity (ctx,
   1273                                  busy_charity_id,
   1274                                  &survivor));
   1275     GNUNET_free (survivor.charity_name);
   1276     GNUNET_free (survivor.charity_url);
   1277     /* A charity without issued receipts is still deletable. */
   1278     {
   1279       uint64_t idle_charity_id;
   1280 
   1281       FAILIF (GNUNET_OK !=
   1282               make_charity (CURRENCY ":1000",
   1283                             &idle_charity_id));
   1284       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
   1285               DONAUDB_delete_charity (ctx,
   1286                                       idle_charity_id,
   1287                                       &in_use));
   1288       FAILIF (in_use);
   1289     }
   1290   }
   1291 
   1292   result = 0;
   1293 
   1294 drop:
   1295   if (0 != result)
   1296     DONAUDB_rollback (ctx);
   1297   GNUNET_break (GNUNET_OK ==
   1298                 DONAUDB_drop_tables (ctx));
   1299 cleanup:
   1300   if (NULL != test_denom_priv.bsign_priv_key)
   1301   {
   1302     TALER_denom_priv_free (&test_denom_priv);
   1303     TALER_denom_pub_free (&test_denom_pub);
   1304   }
   1305   DONAUDB_disconnect (ctx);
   1306   ctx = NULL;
   1307 }
   1308 
   1309 
   1310 int
   1311 main (int argc,
   1312       char *const argv[])
   1313 {
   1314   const char *plugin_name;
   1315   char *config_filename;
   1316   char *testname;
   1317   struct GNUNET_CONFIGURATION_Handle *cfg;
   1318 
   1319   (void) argc;
   1320   result = -1;
   1321   if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
   1322   {
   1323     GNUNET_break (0);
   1324     return -1;
   1325   }
   1326   GNUNET_log_setup (argv[0],
   1327                     "INFO",
   1328                     NULL);
   1329   plugin_name++;
   1330   (void) GNUNET_asprintf (&testname,
   1331                           "test-donau-db-%s",
   1332                           plugin_name);
   1333   (void) GNUNET_asprintf (&config_filename,
   1334                           "%s.conf",
   1335                           testname);
   1336   cfg = GNUNET_CONFIGURATION_create (DONAU_project_data ());
   1337   if (GNUNET_OK !=
   1338       GNUNET_CONFIGURATION_parse (cfg,
   1339                                   config_filename))
   1340   {
   1341     GNUNET_break (0);
   1342     GNUNET_free (config_filename);
   1343     GNUNET_free (testname);
   1344     return 2;
   1345   }
   1346   GNUNET_SCHEDULER_run (&run,
   1347                         cfg);
   1348   GNUNET_CONFIGURATION_destroy (cfg);
   1349   GNUNET_free (config_filename);
   1350   GNUNET_free (testname);
   1351   return result;
   1352 }
   1353 
   1354 
   1355 /* end of test_donaudb.c */