From 08958c73e8ba6ad30e98a30968077cdf55bc86e8 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Tue, 17 Mar 2015 14:54:04 +0100 Subject: add refs to bugtracker --- src/mint/mint_db.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/mint/mint_db.h') diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h index 9818172a..4a9ec152 100644 --- a/src/mint/mint_db.h +++ b/src/mint/mint_db.h @@ -110,8 +110,7 @@ TALER_MINT_DB_rollback (PGconn *db_conn); /** - * Information we keep on a bank transfer that - * established a reserve. + * Information we keep on bank transfer(s) that established a reserve. */ struct BankTransfer { @@ -135,7 +134,7 @@ struct BankTransfer /* FIXME: add functions to add bank transfers to our DB - (and to test if we already did add one) (#3633) */ + (and to test if we already did add one) (#3633/#3717) */ /** * A summary of a Reserve -- cgit v1.2.3 From e6b13123d78c7da4fdcb2cf0e7eb5fafe1f62975 Mon Sep 17 00:00:00 2001 From: Sree Harsha Totakura Date: Wed, 18 Mar 2015 13:50:03 +0100 Subject: update reserve summary when withdraws are made through insert_collectible_blindcoin --- src/mint/mint_db.c | 22 +++++++++++++++++++++- src/mint/mint_db.h | 3 +++ src/mint/taler-mint-httpd_db.c | 1 + src/mint/test_mint_db.c | 9 +++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src/mint/mint_db.h') diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c index 6ed7193e..250acf54 100644 --- a/src/mint/mint_db.c +++ b/src/mint/mint_db.c @@ -1030,6 +1030,8 @@ TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, * * @param db_conn database connection to use * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction * @param collectable corresponding collectable coin (blind signature) * if a coin is found * @return #GNUNET_SYSERR on internal error @@ -1039,9 +1041,11 @@ TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, int TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, const struct CollectableBlindcoin *collectable) { PGresult *result; + struct Reserve reserve; char *denom_pub_enc = NULL; char *denom_sig_enc = NULL; size_t denom_pub_enc_size; @@ -1063,16 +1067,32 @@ TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig), TALER_DB_QUERY_PARAM_END }; + if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) + goto cleanup; result = TALER_DB_exec_prepared (db_conn, "insert_collectable_blindcoin", params); if (PGRES_COMMAND_OK != PQresultStatus (result)) { QUERY_ERR (result); + goto rollback; + } + reserve.pub = (struct GNUNET_CRYPTO_EddsaPublicKey *) + &collectable->reserve_pub; + if (GNUNET_OK != TALER_MINT_DB_reserve_get (db_conn, + &reserve)) + goto rollback; + reserve.balance = TALER_amount_subtract (reserve.balance, withdraw); + if (GNUNET_OK != reserves_update (db_conn, &reserve)) + goto rollback; + if (GNUNET_OK == TALER_MINT_DB_commit (db_conn)) + { + ret = GNUNET_OK; goto cleanup; } - ret = GNUNET_OK; + rollback: + TALER_MINT_DB_rollback(db_conn); cleanup: PQclear (result); GNUNET_free_non_null (denom_pub_enc); diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h index 4a9ec152..cec243c9 100644 --- a/src/mint/mint_db.h +++ b/src/mint/mint_db.h @@ -252,6 +252,8 @@ TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, * * @param db_conn database connection to use * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction * @param collectable corresponding collectable coin (blind signature) * if a coin is found * @return #GNUNET_SYSERR on internal error @@ -261,6 +263,7 @@ TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, int TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, const struct CollectableBlindcoin *collectable); diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index df68b657..c5c121d5 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -386,6 +386,7 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, if (GNUNET_OK != TALER_MINT_DB_insert_collectable_blindcoin (db_conn, &h_blind, + amount_required, &collectable)) { GNUNET_break (0); diff --git a/src/mint/test_mint_db.c b/src/mint/test_mint_db.c index 01041613..6a55a1d3 100644 --- a/src/mint/test_mint_db.c +++ b/src/mint/test_mint_db.c @@ -194,9 +194,18 @@ run (void *cls, char *const *args, const char *cfgfile, cbc.denom_pub = dkp->pub; cbc.sig = GNUNET_CRYPTO_rsa_sign (dkp->priv, &h_blind, sizeof (h_blind)); (void) memcpy (&cbc.reserve_pub, &reserve_pub, sizeof (reserve_pub)); + amount.value--; + amount.fraction--; FAILIF (GNUNET_OK != TALER_MINT_DB_insert_collectable_blindcoin (db, &h_blind, + amount, &cbc)); + FAILIF (GNUNET_OK != check_reserve (db, + &reserve_pub, + amount.value, + amount.fraction, + amount.currency, + expiry.abs_value_us)); FAILIF (GNUNET_YES != TALER_MINT_DB_get_collectable_blindcoin (db, &h_blind, &cbc2)); -- cgit v1.2.3 From 23bf1eee74bed73cf98264c247ab44df8dadfcd9 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 18 Mar 2015 18:55:41 +0100 Subject: fix #3716: make sure amount-API offers proper checks against overflow and other issues --- src/include/taler_amount_lib.h | 121 ++++++--- src/include/taler_json_lib.h | 2 +- src/lib/mint_api.c | 12 +- src/mint/mint_db.c | 25 +- src/mint/mint_db.h | 2 +- src/mint/taler-mint-httpd_db.c | 143 ++++++---- src/mint/taler-mint-httpd_deposit.c | 3 +- src/mint/taler-mint-httpd_keystate.c | 21 +- src/mint/taler-mint-httpd_parsing.c | 14 +- src/mint/taler-mint-httpd_refresh.c | 40 ++- src/mint/taler-mint-httpd_responses.c | 68 +++-- src/mint/taler-mint-keyup.c | 27 +- src/mint/taler-mint-reservemod.c | 8 +- src/util/amount.c | 492 +++++++++++++++++++++------------- src/util/json.c | 21 +- 15 files changed, 660 insertions(+), 339 deletions(-) (limited to 'src/mint/mint_db.h') diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h index 8bea0256..e64ff4d9 100644 --- a/src/include/taler_amount_lib.h +++ b/src/include/taler_amount_lib.h @@ -25,10 +25,27 @@ /** * Number of characters (plus 1 for 0-termination) we use to * represent currency names (i.e. EUR, USD, etc.). We use - * 8 for alignment (!). + * 4 for alignment as 3 characters are typical and we need a + * 0-terminator. So do not change this. */ #define TALER_CURRENCY_LEN 4 +/** + * The "fraction" value in a `struct TALER_Amount` represents which + * fraction of the "main" value? + * + * Note that we need sub-cent precision here as transaction fees might + * be that low, and as we want to support microdonations. + */ +#define TALER_AMOUNT_FRAC_BASE 1000000 + +/** + * How many digits behind the comma are required to represent the + * fractional value in human readable decimal format? Must match + * lg(#TALER_AMOUNT_FRAC_BASE). + */ +#define TALER_AMOUNT_FRAC_LEN 6 + GNUNET_NETWORK_STRUCT_BEGIN @@ -41,12 +58,12 @@ struct TALER_AmountNBO /** * Value in the main currency, in NBO. */ - uint32_t value; + uint64_t value GNUNET_PACKED; /** * Additinal fractional value, in NBO. */ - uint32_t fraction; + uint32_t fraction GNUNET_PACKED; /** * Type of the currency being represented. @@ -65,7 +82,7 @@ struct TALER_Amount /** * Value (numerator of fraction) */ - uint32_t value; + uint64_t value; /** * Fraction (denominator of fraction) @@ -73,7 +90,8 @@ struct TALER_Amount uint32_t fraction; /** - * Currency string, left adjusted and padded with zeros. + * Currency string, left adjusted and padded with zeros. All zeros + * for "invalid" values. */ char currency[TALER_CURRENCY_LEN]; }; @@ -92,82 +110,123 @@ TALER_string_to_amount (const char *str, struct TALER_Amount *denom); +/** + * Get the value of "zero" in a particular currency. + * + * @param cur currency description + * @param denom denomination to write the result to + * @return #GNUNET_OK if @a cur is a valid currency specification, + * #GNUNET_SYSERR if it is invalid. + */ +int +TALER_amount_get_zero (const char *cur, + struct TALER_Amount *denom); + + /** * Convert amount from host to network representation. * + * @param res where to store amount in network representation * @param d amount in host representation - * @return amount in network representation */ -struct TALER_AmountNBO -TALER_amount_hton (const struct TALER_Amount d); +void +TALER_amount_hton (struct TALER_AmountNBO *res, + const struct TALER_Amount *d); /** * Convert amount from network to host representation. * + * @param res where to store amount in host representation * @param d amount in network representation - * @return amount in host representation */ -struct TALER_Amount -TALER_amount_ntoh (const struct TALER_AmountNBO dn); +void +TALER_amount_ntoh (struct TALER_Amount *res, + const struct TALER_AmountNBO *dn); /** - * Compare the value/fraction of two amounts. Does not compare the currency, - * i.e. comparing amounts with the same value and fraction but different - * currency would return 0. + * Compare the value/fraction of two amounts. Does not compare the currency. + * Comparing amounts of different currencies will cause the program to abort(). + * If unsure, check with #TALER_amount_cmp_currency() first to be sure that + * the currencies of the two amounts are identical. * * @param a1 first amount * @param a2 second amount * @return result of the comparison */ int -TALER_amount_cmp (struct TALER_Amount a1, - struct TALER_Amount a2); +TALER_amount_cmp (const struct TALER_Amount *a1, + const struct TALER_Amount *a2); + + +/** + * Test if @a a1 and @a a2 are the same currency. + * + * @param a1 amount to test + * @param a2 amount to test + * @return #GNUNET_YES if @a a1 and @a a2 are the same currency + * #GNUNET_NO if the currencies are different + * #GNUNET_SYSERR if either amount is invalid + */ +int +TALER_amount_cmp_currency (const struct TALER_Amount *a1, + const struct TALER_Amount *a2); /** * Perform saturating subtraction of amounts. * + * @param diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1 * @param a1 amount to subtract from * @param a2 amount to subtract - * @return (a1-a2) or 0 if a2>=a1 + * @return #GNUNET_OK if the subtraction worked, + * #GNUNET_NO if @a a1 = @a a2 + * #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible; + * @a diff is set to invalid */ -struct TALER_Amount -TALER_amount_subtract (struct TALER_Amount a1, - struct TALER_Amount a2); +int +TALER_amount_subtract (struct TALER_Amount *diff, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2); /** - * Perform saturating addition of amounts + * Perform addition of amounts. * + * @param sum where to store @a a1 + @a a2, set to "invalid" on overflow * @param a1 first amount to add * @param a2 second amount to add - * @return sum of a1 and a2 + * @return #GNUNET_OK if the addition worked, + * #GNUNET_SYSERR on overflow */ -struct TALER_Amount -TALER_amount_add (struct TALER_Amount a1, - struct TALER_Amount a2); +int +TALER_amount_add (struct TALER_Amount *sum, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2); /** * Normalize the given amount. * - * @param amout amount to normalize - * @return normalized amount + * @param amount amount to normalize + * @return #GNUNET_OK if normalization worked + * #GNUNET_NO if value was already normalized + * #GNUNET_SYSERR if value was invalid or could not be normalized */ -struct TALER_Amount -TALER_amount_normalize (struct TALER_Amount amount); +int +TALER_amount_normalize (struct TALER_Amount *amount); /** * Convert amount to string. * * @param amount amount to convert to string - * @return freshly allocated string representation + * @return freshly allocated string representation, + * NULL if the @a amount was invalid */ char * -TALER_amount_to_string (struct TALER_Amount amount); +TALER_amount_to_string (const struct TALER_Amount *amount); #endif diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 1048b89f..c5515966 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -39,7 +39,7 @@ * @return a json object describing the amount */ json_t * -TALER_JSON_from_amount (struct TALER_Amount amount); +TALER_JSON_from_amount (const struct TALER_Amount *amount); /** diff --git a/src/lib/mint_api.c b/src/lib/mint_api.c index af4f0495..eb2697a2 100644 --- a/src/lib/mint_api.c +++ b/src/lib/mint_api.c @@ -412,10 +412,14 @@ parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key, denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); - denom_key_issue.value = TALER_amount_hton (value); - denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw); - denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit); - denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh); + TALER_amount_hton (&denom_key_issue.value, + &value); + TALER_amount_hton (&denom_key_issue.fee_withdraw, + &fee_withdraw); + TALER_amount_hton (&denom_key_issue.fee_deposit, + &fee_deposit); + TALER_amount_hton (&denom_key_issue.fee_refresh, + &fee_refresh); EXITIF (GNUNET_SYSERR == GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, &denom_key_issue.purpose, diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c index 6ed7193e..545a5252 100644 --- a/src/mint/mint_db.c +++ b/src/mint/mint_db.c @@ -807,7 +807,8 @@ reserves_update (PGconn *db, TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), TALER_DB_QUERY_PARAM_END }; - balance_nbo = TALER_amount_hton (reserve->balance); + TALER_amount_hton (&balance_nbo, + &reserve->balance); expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); result = TALER_DB_exec_prepared (db, "update_reserve", @@ -837,7 +838,7 @@ reserves_update (PGconn *db, int TALER_MINT_DB_reserves_in_insert (PGconn *db, struct Reserve *reserve, - const struct TALER_Amount balance, + const struct TALER_Amount *balance, const struct GNUNET_TIME_Absolute expiry) { struct TALER_AmountNBO balance_nbo; @@ -862,7 +863,8 @@ TALER_MINT_DB_reserves_in_insert (PGconn *db, TALER_MINT_DB_rollback (db); return GNUNET_SYSERR; } - balance_nbo = TALER_amount_hton (balance); + TALER_amount_hton (&balance_nbo, + balance); expiry_nbo = GNUNET_TIME_absolute_hton (expiry); if (GNUNET_NO == reserve_exists) { @@ -907,19 +909,27 @@ TALER_MINT_DB_reserves_in_insert (PGconn *db, QUERY_ERR (result); goto rollback; } - PQclear (result); result = NULL; + PQclear (result); + result = NULL; if (GNUNET_NO == reserve_exists) { if (GNUNET_OK != TALER_MINT_DB_commit (db)) return GNUNET_SYSERR; - reserve->balance = balance; + reserve->balance = *balance; reserve->expiry = expiry; return GNUNET_OK; } /* Update reserve */ struct Reserve updated_reserve; updated_reserve.pub = reserve->pub; - updated_reserve.balance = TALER_amount_add (reserve->balance, balance); + + if (GNUNET_OK != + TALER_amount_add (&updated_reserve.balance, + &reserve->balance, + balance)) + { + return GNUNET_SYSERR; + } updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); if (GNUNET_OK != reserves_update (db, &updated_reserve)) goto rollback; @@ -1350,7 +1360,8 @@ TALER_MINT_DB_insert_deposit (PGconn *db_conn, GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig, &denom_sig_enc); json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); - amount_nbo = TALER_amount_hton (deposit->amount); + TALER_amount_hton (&amount_nbo, + &deposit->amount); struct TALER_DB_QueryParam params[]= { TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h index 4a9ec152..9312f548 100644 --- a/src/mint/mint_db.h +++ b/src/mint/mint_db.h @@ -224,7 +224,7 @@ TALER_MINT_DB_reserve_get (PGconn *db, int TALER_MINT_DB_reserves_in_insert (PGconn *db, struct Reserve *reserve, - const struct TALER_Amount balance, + const struct TALER_Amount *balance, const struct GNUNET_TIME_Absolute expiry); diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index df68b657..05c2a48a 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -34,26 +34,6 @@ #include "taler-mint-httpd_keystate.h" -/** - * Get an amount in the mint's currency that is zero. - * - * @return zero amount in the mint's currency - */ -static struct TALER_Amount -mint_amount_native_zero () -{ - struct TALER_Amount amount; - - memset (&amount, - 0, - sizeof (amount)); - memcpy (amount.currency, - MINT_CURRENCY, - strlen (MINT_CURRENCY) + 1); - return amount; -} - - /** * Execute a deposit. The validity of the coin and signature * have already been checked. The database must now check that @@ -99,9 +79,12 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, mks = TALER_MINT_key_state_acquire (); dki = TALER_MINT_get_denom_key (mks, deposit->coin.denom_pub); - value = TALER_amount_ntoh (dki->issue.value); - fee_deposit = TALER_amount_ntoh (dki->issue.fee_deposit); - fee_refresh = TALER_amount_ntoh (dki->issue.fee_refresh); + TALER_amount_ntoh (&value, + &dki->issue.value); + TALER_amount_ntoh (&fee_deposit, + &dki->issue.fee_deposit); + TALER_amount_ntoh (&fee_refresh, + &dki->issue.fee_refresh); TALER_MINT_key_state_release (mks); if (GNUNET_OK != @@ -113,26 +96,49 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, tl = TALER_MINT_DB_get_coin_transactions (db_conn, &deposit->coin.coin_pub); spent = fee_deposit; /* fee for THIS transaction */ - /* FIXME: need to deal better with integer overflows - in the logic that follows! (change amount.c API! -- #3637) */ - spent = TALER_amount_add (spent, - deposit->amount); + if (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &deposit->amount)) + { + GNUNET_break (0); + TALER_MINT_DB_free_coin_transaction_list (tl); + return TALER_MINT_reply_internal_db_error (connection); + } for (pos = tl; NULL != pos; pos = pos->next) { switch (pos->type) { case TALER_MINT_DB_TT_DEPOSIT: - spent = TALER_amount_add (spent, - pos->details.deposit->amount); - spent = TALER_amount_add (spent, - fee_deposit); + if ( (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.deposit->amount)) || + (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &fee_deposit)) ) + { + GNUNET_break (0); + TALER_MINT_DB_free_coin_transaction_list (tl); + return TALER_MINT_reply_internal_db_error (connection); + } break; case TALER_MINT_DB_TT_REFRESH_MELT: - spent = TALER_amount_add (spent, - pos->details.melt->amount); - spent = TALER_amount_add (spent, - fee_refresh); + if ( (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &pos->details.melt->amount)) || + (GNUNET_OK != + TALER_amount_add (&spent, + &spent, + &fee_refresh)) ) + { + GNUNET_break (0); + TALER_MINT_DB_free_coin_transaction_list (tl); + return TALER_MINT_reply_internal_db_error (connection); + } break; case TALER_MINT_DB_TT_LOCK: /* should check if lock is still active, @@ -146,11 +152,12 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, } } - if (0 < TALER_amount_cmp (spent, value)) + if (0 < TALER_amount_cmp (&spent, + &value)) { TALER_MINT_DB_rollback (db_conn); ret = TALER_MINT_reply_deposit_insufficient_funds (connection, - tl); + tl); TALER_MINT_DB_free_coin_transaction_list (tl); return ret; } @@ -251,6 +258,7 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, struct TALER_Amount withdraw_total; struct TALER_Amount balance; struct TALER_Amount value; + struct TALER_Amount fee_withdraw; struct GNUNET_HashCode h_blind; int res; @@ -318,8 +326,20 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, } /* calculate amount required including fees */ - amount_required = TALER_amount_add (TALER_amount_ntoh (dki->issue.value), - TALER_amount_ntoh (dki->issue.fee_withdraw)); + TALER_amount_ntoh (&value, + &dki->issue.value); + TALER_amount_ntoh (&fee_withdraw, + &dki->issue.fee_withdraw); + + if (GNUNET_OK != + TALER_amount_add (&amount_required, + &value, + &fee_withdraw)) + { + TALER_MINT_DB_rollback (db_conn); + TALER_MINT_key_state_release (key_state); + return TALER_MINT_reply_internal_db_error (connection); + } /* calculate balance of the reserve */ res = 0; @@ -331,29 +351,45 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, if (0 == (res & 1)) deposit_total = pos->details.bank->amount; else - deposit_total = TALER_amount_add (deposit_total, - pos->details.bank->amount); + if (GNUNET_OK != + TALER_amount_add (&deposit_total, + &deposit_total, + &pos->details.bank->amount)) + { + TALER_MINT_DB_rollback (db_conn); + TALER_MINT_key_state_release (key_state); + return TALER_MINT_reply_internal_db_error (connection); + } res |= 1; break; case TALER_MINT_DB_RO_WITHDRAW_COIN: tdki = TALER_MINT_get_denom_key (key_state, pos->details.withdraw->denom_pub); - value = TALER_amount_ntoh (tdki->issue.value); + TALER_amount_ntoh (&value, + &tdki->issue.value); if (0 == (res & 2)) withdraw_total = value; else - withdraw_total = TALER_amount_add (withdraw_total, - value); + if (GNUNET_OK != + TALER_amount_add (&withdraw_total, + &withdraw_total, + &value)) + { + TALER_MINT_DB_rollback (db_conn); + TALER_MINT_key_state_release (key_state); + return TALER_MINT_reply_internal_db_error (connection); + } res |= 2; break; } } - GNUNET_break (0 > TALER_amount_cmp (withdraw_total, - deposit_total)); - balance = TALER_amount_subtract (deposit_total, - withdraw_total); - if (0 < TALER_amount_cmp (amount_required, - balance)) + /* All reserve balances should be non-negative */ + GNUNET_break (GNUNET_SYSERR != + TALER_amount_subtract (&balance, + &deposit_total, + &withdraw_total)); + if (0 < TALER_amount_cmp (&amount_required, + &balance)) { TALER_MINT_key_state_release (key_state); TALER_MINT_DB_rollback (db_conn); @@ -450,7 +486,8 @@ refresh_accept_melts (struct MHD_Connection *connection, "denom not found")) ? GNUNET_NO : GNUNET_SYSERR; - coin_value = TALER_amount_ntoh (dki->value); + TALER_amount_ntoh (&coin_value, + &dki->value); tl = TALER_MINT_DB_get_coin_transactions (db_conn, &coin_public_info->coin_pub); /* FIXME: #3636: compute how much value is left with this coin and @@ -459,8 +496,8 @@ refresh_accept_melts (struct MHD_Connection *connection, /* Refuse to refresh when the coin does not have enough money left to * pay the refreshing fees of the coin. */ - if (TALER_amount_cmp (coin_residual, - coin_details->melt_amount) < 0) + if (TALER_amount_cmp (&coin_residual, + &coin_details->melt_amount) < 0) { res = (MHD_YES == TALER_MINT_reply_refresh_melt_insufficient_funds (connection, diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c index d37e69e4..37d6d23d 100644 --- a/src/mint/taler-mint-httpd_deposit.c +++ b/src/mint/taler-mint-httpd_deposit.c @@ -65,7 +65,8 @@ verify_and_execute_deposit (struct MHD_Connection *connection, dr.h_contract = deposit->h_contract; dr.h_wire = deposit->h_wire; dr.transaction_id = GNUNET_htonll (deposit->transaction_id); - dr.amount = TALER_amount_hton (deposit->amount); + TALER_amount_hton (&dr.amount, + &deposit->amount); dr.coin_pub = deposit->coin.coin_pub; if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_WALLET_DEPOSIT, diff --git a/src/mint/taler-mint-httpd_keystate.c b/src/mint/taler-mint-httpd_keystate.c index bf802f5b..ca802e2d 100644 --- a/src/mint/taler-mint-httpd_keystate.c +++ b/src/mint/taler-mint-httpd_keystate.c @@ -114,6 +114,19 @@ static json_t * denom_key_issue_to_json (struct GNUNET_CRYPTO_rsa_PublicKey *pk, const struct TALER_MINT_DenomKeyIssue *dki) { + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + + TALER_amount_ntoh (&value, + &dki->value); + TALER_amount_ntoh (&fee_withdraw, + &dki->fee_withdraw); + TALER_amount_ntoh (&fee_deposit, + &dki->fee_deposit); + TALER_amount_ntoh (&fee_refresh, + &dki->fee_refresh); return json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "master_sig", @@ -128,13 +141,13 @@ denom_key_issue_to_json (struct GNUNET_CRYPTO_rsa_PublicKey *pk, "denom_pub", TALER_JSON_from_rsa_public_key (pk), "value", - TALER_JSON_from_amount (TALER_amount_ntoh (dki->value)), + TALER_JSON_from_amount (&value), "fee_withdraw", - TALER_JSON_from_amount (TALER_amount_ntoh (dki->fee_withdraw)), + TALER_JSON_from_amount (&fee_withdraw), "fee_deposit", - TALER_JSON_from_amount (TALER_amount_ntoh (dki->fee_deposit)), + TALER_JSON_from_amount (&fee_deposit), "fee_refresh", - TALER_JSON_from_amount (TALER_amount_ntoh (dki->fee_refresh))); + TALER_JSON_from_amount (&fee_refresh)); } diff --git a/src/mint/taler-mint-httpd_parsing.c b/src/mint/taler-mint-httpd_parsing.c index 6c5f72b3..b8bc043e 100644 --- a/src/mint/taler-mint-httpd_parsing.c +++ b/src/mint/taler-mint-httpd_parsing.c @@ -878,8 +878,10 @@ TALER_MINT_parse_amount_json (struct MHD_Connection *connection, json_int_t value; json_int_t fraction; const char *currency; - struct TALER_Amount a; + memset (amount, + 0, + sizeof (struct TALER_Amount)); if (-1 == json_unpack (f, "{s:I, s:I, s:s}", "value", &value, @@ -897,7 +899,7 @@ TALER_MINT_parse_amount_json (struct MHD_Connection *connection, } if ( (value < 0) || (fraction < 0) || - (value > UINT32_MAX) || + (value > UINT64_MAX) || (fraction > UINT32_MAX) ) { LOG_WARNING ("Amount specified not in allowed range\n"); @@ -922,11 +924,11 @@ TALER_MINT_parse_amount_json (struct MHD_Connection *connection, return GNUNET_SYSERR; return GNUNET_NO; } - a.value = (uint32_t) value; - a.fraction = (uint32_t) fraction; + amount->value = (uint64_t) value; + amount->fraction = (uint32_t) fraction; GNUNET_assert (strlen (MINT_CURRENCY) < TALER_CURRENCY_LEN); - strcpy (a.currency, MINT_CURRENCY); - *amount = TALER_amount_normalize (a); + strcpy (amount->currency, MINT_CURRENCY); + TALER_amount_normalize (amount); return GNUNET_OK; } diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c index c7bda5a7..e62521ef 100644 --- a/src/mint/taler-mint-httpd_refresh.c +++ b/src/mint/taler-mint-httpd_refresh.c @@ -82,6 +82,8 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, struct TALER_Amount cost; struct TALER_Amount total_cost; struct TALER_Amount melt; + struct TALER_Amount value; + struct TALER_Amount fee_withdraw; struct TALER_Amount total_melt; /* check that signature from the session public key is ok */ @@ -107,7 +109,8 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_SESSION); body.purpose.size = htonl (sizeof (struct RefreshMeltSessionSignature)); body.melt_hash = melt_hash; - body.amount = TALER_amount_hton (coin_melt_details->melt_amount); + TALER_amount_hton (&body.amount, + &coin_melt_details->melt_amount); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_REFRESH_MELT_SESSION, &body.purpose, @@ -130,11 +133,22 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, { dki = &TALER_MINT_get_denom_key (key_state, denom_pubs[i])->issue; - cost = TALER_amount_add (TALER_amount_ntoh (dki->value), - TALER_amount_ntoh (dki->fee_withdraw)); + TALER_amount_ntoh (&value, + &dki->value); + TALER_amount_ntoh (&fee_withdraw, + &dki->fee_withdraw); // FIXME: #3637 - total_cost = TALER_amount_add (cost, - total_cost); + if ( (GNUNET_OK != + TALER_amount_add (&cost, + &value, + &fee_withdraw)) || + (GNUNET_OK != + TALER_amount_add (&total_cost, + &cost, + &total_cost)) ) + { + // FIXME... + } } // FIXME: badness, use proper way to set to zero... @@ -146,13 +160,18 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, // melt = coin_values[i]; // FIXME: #3636! // FIXME: #3637 - total_melt = TALER_amount_add (melt, - total_melt); + if (GNUNET_OK != + TALER_amount_add (&total_melt, + &melt, + &total_melt)) + { + // FIXME ... + } } TALER_MINT_key_state_release (key_state); if (0 != - TALER_amount_cmp (total_cost, - total_melt) ) + TALER_amount_cmp (&total_cost, + &total_melt) ) { /* We require total value of coins being melted and total value of coins being generated to match! */ @@ -269,7 +288,8 @@ verify_coin_public_info (struct MHD_Connection *connection, body.purpose.size = htonl (sizeof (struct RefreshMeltCoinSignature)); body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_COIN); body.melt_hash = *melt_hash; - body.amount = TALER_amount_hton (r_melt_detail->melt_amount); + TALER_amount_hton (&body.amount, + &r_melt_detail->melt_amount); body.coin_pub = r_public_info->coin_pub; if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_REFRESH_MELT_COIN, diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index 3d827b11..a4b44215 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -299,7 +299,8 @@ TALER_MINT_reply_deposit_success (struct MHD_Connection *connection, dc.h_contract = *h_contract; dc.h_wire = *h_wire; dc.transaction_id = GNUNET_htonll (transaction_id); - dc.amount = TALER_amount_hton (*amount); + TALER_amount_hton (&dc.amount, + amount); dc.coin_pub = *coin_pub; dc.merchant = *merchant; TALER_MINT_keys_sign (&dc.purpose, @@ -346,7 +347,8 @@ compile_transaction_history (const struct TALER_MINT_DB_TransactionList *tl) dr.h_contract = deposit->h_contract; dr.h_wire = deposit->h_wire; dr.transaction_id = GNUNET_htonll (deposit->transaction_id); - dr.amount = TALER_amount_hton (deposit->amount); + TALER_amount_hton (&dr.amount, + &deposit->amount); dr.coin_pub = deposit->coin.coin_pub; transaction = TALER_JSON_from_ecdsa_sig (&dr.purpose, &deposit->csig); @@ -362,7 +364,8 @@ compile_transaction_history (const struct TALER_MINT_DB_TransactionList *tl) ms.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_COIN); ms.purpose.size = htonl (sizeof (struct RefreshMeltCoinSignature)); ms.melt_hash = melt->melt_hash; - ms.amount = TALER_amount_hton (melt->amount); + TALER_amount_hton (&ms.amount, + &melt->amount); ms.coin_pub = melt->coin.coin_pub; transaction = TALER_JSON_from_ecdsa_sig (&ms.purpose, &melt->coin_sig); @@ -382,7 +385,7 @@ compile_transaction_history (const struct TALER_MINT_DB_TransactionList *tl) json_array_append_new (history, json_pack ("{s:s, s:o}", "type", type, - "amount", TALER_JSON_from_amount (value), + "amount", TALER_JSON_from_amount (&value), "signature", transaction)); } return history; @@ -419,7 +422,7 @@ TALER_MINT_reply_deposit_insufficient_funds (struct MHD_Connection *connection, * * @param rh reserve history to JSON-ify * @param balance[OUT] set to current reserve balance - * @return json representation of the @a rh + * @return json representation of the @a rh, NULL on error */ static json_t * compile_reserve_history (const struct ReserveHistory *rh, @@ -446,14 +449,20 @@ compile_reserve_history (const struct ReserveHistory *rh, if (0 == ret) deposit_total = pos->details.bank->amount; else - deposit_total = TALER_amount_add (deposit_total, - pos->details.bank->amount); + if (GNUNET_OK != + TALER_amount_add (&deposit_total, + &deposit_total, + &pos->details.bank->amount)) + { + json_decref (json_history); + return NULL; + } ret = 1; json_array_append_new (json_history, json_pack ("{s:s, s:o, s:o}", "type", "DEPOSIT", "wire", pos->details.bank->wire, - "amount", TALER_JSON_from_amount (pos->details.bank->amount))); + "amount", TALER_JSON_from_amount (&pos->details.bank->amount))); break; case TALER_MINT_DB_RO_WITHDRAW_COIN: break; @@ -472,12 +481,20 @@ compile_reserve_history (const struct ReserveHistory *rh, dki = TALER_MINT_get_denom_key (key_state, pos->details.withdraw->denom_pub); - value = TALER_amount_ntoh (dki->issue.value); + TALER_amount_ntoh (&value, + &dki->issue.value); if (0 == ret) withdraw_total = value; else - withdraw_total = TALER_amount_add (withdraw_total, - value); + if (GNUNET_OK != + TALER_amount_add (&withdraw_total, + &withdraw_total, + &value)) + { + TALER_MINT_key_state_release (key_state); + json_decref (json_history); + return NULL; + } ret = 1; wr.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW); wr.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest)); @@ -493,15 +510,22 @@ compile_reserve_history (const struct ReserveHistory *rh, json_pack ("{s:s, s:o, s:o}", "type", "WITHDRAW", "signature", transaction, - "amount", TALER_JSON_from_amount (value))); + "amount", TALER_JSON_from_amount (&value))); break; } } TALER_MINT_key_state_release (key_state); - *balance = TALER_amount_subtract (deposit_total, - withdraw_total); + if (GNUNET_SYSERR == + TALER_amount_subtract (balance, + &deposit_total, + &withdraw_total)) + { + GNUNET_break (0); + json_decref (json_history); + return NULL; + } return json_history; } @@ -524,7 +548,10 @@ TALER_MINT_reply_withdraw_status_success (struct MHD_Connection *connection, json_history = compile_reserve_history (rh, &balance); - json_balance = TALER_JSON_from_amount (balance); + if (NULL == json_history) + return TALER_MINT_reply_internal_error (connection, + "balance calculation failure"); + json_balance = TALER_JSON_from_amount (&balance); ret = TALER_MINT_reply_json_pack (connection, MHD_HTTP_OK, "{s:o, s:o}", @@ -556,7 +583,10 @@ TALER_MINT_reply_withdraw_sign_insufficient_funds (struct MHD_Connection *connec json_history = compile_reserve_history (rh, &balance); - json_balance = TALER_JSON_from_amount (balance); + if (NULL == json_history) + return TALER_MINT_reply_internal_error (connection, + "balance calculation failure"); + json_balance = TALER_JSON_from_amount (&balance); ret = TALER_MINT_reply_json_pack (connection, MHD_HTTP_PAYMENT_REQUIRED, "{s:s, s:o, s:o}", @@ -625,9 +655,9 @@ TALER_MINT_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connect "error", "insufficient funds", "coin-pub", TALER_JSON_from_data (coin_pub, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)), - "original-value", TALER_JSON_from_amount (coin_value), - "residual-value", TALER_JSON_from_amount (residual), - "requested-value", TALER_JSON_from_amount (requested), + "original-value", TALER_JSON_from_amount (&coin_value), + "residual-value", TALER_JSON_from_amount (&residual), + "requested-value", TALER_JSON_from_amount (&requested), "history", history); } diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c index 33bb8724..759e7c1b 100644 --- a/src/mint/taler-mint-keyup.c +++ b/src/mint/taler-mint-keyup.c @@ -233,10 +233,14 @@ hash_coin_type (const struct CoinTypeParams *p, sizeof (struct CoinTypeNBO)); p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); - p_nbo.value = TALER_amount_hton (p->value); - p_nbo.fee_withdraw = TALER_amount_hton (p->fee_withdraw); - p_nbo.fee_deposit = TALER_amount_hton (p->fee_deposit); - p_nbo.fee_refresh = TALER_amount_hton (p->fee_refresh); + TALER_amount_hton (&p_nbo.value, + &p->value); + TALER_amount_hton (&p_nbo.fee_withdraw, + &p->fee_withdraw); + TALER_amount_hton (&p_nbo.fee_deposit, + &p->fee_deposit); + TALER_amount_hton (&p_nbo.fee_refresh, + &p->fee_refresh); p_nbo.rsa_keysize = htonl (p->rsa_keysize); GNUNET_CRYPTO_hash (&p_nbo, sizeof (struct CoinTypeNBO), @@ -273,7 +277,7 @@ get_cointype_dir (const struct CoinTypeParams *p) GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); hash_str[HASH_CUTOFF] = 0; - val_str = TALER_amount_to_string (p->value); + val_str = TALER_amount_to_string (&p->value); for (i = 0; i < strlen (val_str); i++) if ( (':' == val_str[i]) || ('.' == val_str[i]) ) @@ -687,11 +691,14 @@ create_denomkey_issue (const struct CoinTypeParams *params, dki->issue.expire_spend = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, params->duration_spend)); - dki->issue.value = TALER_amount_hton (params->value); - dki->issue.fee_withdraw = TALER_amount_hton (params->fee_withdraw); - dki->issue.fee_deposit = TALER_amount_hton (params->fee_deposit); - dki->issue.fee_refresh = TALER_amount_hton (params->fee_refresh); - + TALER_amount_hton (&dki->issue.value, + ¶ms->value); + TALER_amount_hton (&dki->issue.fee_withdraw, + ¶ms->fee_withdraw); + TALER_amount_hton (&dki->issue.fee_deposit, + ¶ms->fee_deposit); + TALER_amount_hton (&dki->issue.fee_refresh, + ¶ms->fee_refresh); dki->issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); dki->issue.purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssuePriv) - offsetof (struct TALER_MINT_DenomKeyIssuePriv, diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c index dea59d6e..df3f0cd5 100644 --- a/src/mint/taler-mint-reservemod.c +++ b/src/mint/taler-mint-reservemod.c @@ -161,9 +161,11 @@ reservemod_add (struct TALER_Amount denom) "balance_fraction", "balance_currency", &old_denom)); - new_denom = TALER_amount_add (old_denom, - denom); - new_denom_nbo = TALER_amount_hton (new_denom); + TALER_amount_add (&new_denom, + &old_denom, + &denom); + TALER_amount_hton (&new_denom_nbo, + &new_denom); result = PQexecParams (db_conn, "UPDATE reserves" " SET balance_value = $1, balance_fraction = $2, status_sig = NULL, status_sign_pub = NULL" diff --git a/src/util/amount.c b/src/util/amount.c index b3e3b421..5e7f69fd 100644 --- a/src/util/amount.c +++ b/src/util/amount.c @@ -30,16 +30,6 @@ #include "taler_util.h" #include -/** - * - */ -#define AMOUNT_FRAC_BASE 1000000 - -/** - * - */ -#define AMOUNT_FRAC_LEN 6 - /** * Parse money amount description, in the format "A:B.C". @@ -53,159 +43,260 @@ int TALER_string_to_amount (const char *str, struct TALER_Amount *denom) { - size_t i; // pos in str - int n; // number tmp - size_t c; // currency pos - uint32_t b; // base for suffix + size_t i; + int n; + uint32_t b; + const char *colon; + const char *value; memset (denom, 0, sizeof (struct TALER_Amount)); - - i = 0; - while (isspace(str[i])) - i++; - - if (0 == str[i]) + /* skip leading whitespace */ + while (isspace(str[0])) + str++; + if ('\0' == str[0]) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "null before currency\n"); + "Null before currency\n"); return GNUNET_SYSERR; } - - c = 0; - while (str[i] != ':') + /* parse currency */ + colon = strchr (str, (int) ':'); + if ( (NULL == colon) || + ((colon - str) >= TALER_CURRENCY_LEN) ) { - if (0 == str[i]) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "null before colon"); - return GNUNET_SYSERR; - } - if (c > 3) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "currency too long\n"); - return GNUNET_SYSERR; - } - denom->currency[c] = str[i]; - c++; - i++; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid currency specified before colon: `%s'", + str); + goto fail; } - - // skip colon - i++; - - if (0 == str[i]) + memcpy (denom->currency, + str, + colon - str); + /* skip colon */ + value = colon + 1; + if ('\0' == value[0]) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "null before value\n"); - return GNUNET_SYSERR; + "Null before value\n"); + goto fail; } - while (str[i] != '.') + /* parse value */ + i = 0; + while ('.' != value[i]) { - if (0 == str[i]) + if ('\0' == value[i]) { return GNUNET_OK; } + if ( (str[i] < '0') || (str[i] > '9') ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid character `%c'\n", + str[i]); + goto fail; + } n = str[i] - '0'; - if (n < 0 || n > 9) + if (denom->value * 10 + n < denom->value) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "invalid character '%c' before comma at %u\n", - (char) n, - i); - return GNUNET_SYSERR; + "Value too large\n"); + goto fail; } denom->value = (denom->value * 10) + n; i++; } - // skip the dot + /* skip the dot */ i++; - if (0 == str[i]) + /* parse fraction */ + if ('\0' == str[i]) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "null after dot"); - return GNUNET_SYSERR; + "Null after dot"); + goto fail; } - - b = 100000; - - while (0 != str[i]) + b = TALER_AMOUNT_FRAC_BASE / 10; + while ('\0' != str[i]) { - n = str[i] - '0'; - if ( (0 == b) || (n < 0) || (n > 9) ) + if (0 == b) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "error after comma"); - return GNUNET_SYSERR; + "Fractional value too small (only %u digits supported)", + (unsigned int) TALER_AMOUNT_FRAC_LEN); + goto fail; + } + if ( (str[i] < '0') || (str[i] > '9') ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Error after comma"); + goto fail; } + n = str[i] - '0'; denom->fraction += n * b; b /= 10; i++; } - return GNUNET_OK; + + fail: + /* set currency to 'invalid' to prevent accidental use */ + memset (denom->currency, + 0, + TALER_CURRENCY_LEN); + return GNUNET_SYSERR; +} + + +/** + * Convert amount from host to network representation. + * + * @param res where to store amount in network representation + * @param d amount in host representation + */ +void +TALER_amount_hton (struct TALER_AmountNBO *res, + const struct TALER_Amount *d) +{ + res->value = GNUNET_htonll (d->value); + res->fraction = htonl (d->fraction); + memcpy (res->currency, + d->currency, + TALER_CURRENCY_LEN); +} + + +/** + * Convert amount from network to host representation. + * + * @param res where to store amount in host representation + * @param d amount in network representation + */ +void +TALER_amount_ntoh (struct TALER_Amount *res, + const struct TALER_AmountNBO *dn) +{ + res->value = GNUNET_ntohll (dn->value); + res->fraction = ntohl (dn->fraction); + memcpy (res->currency, + dn->currency, + TALER_CURRENCY_LEN); } /** - * FIXME + * Get the value of "zero" in a particular currency. + * + * @param cur currency description + * @param denom denomination to write the result to + * @return #GNUNET_OK if @a cur is a valid currency specification, + * #GNUNET_SYSERR if it is invalid. */ -struct TALER_AmountNBO -TALER_amount_hton (const struct TALER_Amount d) +int +TALER_amount_get_zero (const char *cur, + struct TALER_Amount *denom) { - struct TALER_AmountNBO dn; - dn.value = htonl (d.value); - dn.fraction = htonl (d.fraction); - memcpy (dn.currency, d.currency, TALER_CURRENCY_LEN); + size_t slen; - return dn; + slen = strlen (cur); + if (slen >= TALER_CURRENCY_LEN) + return GNUNET_SYSERR; + memset (denom, + 0, + sizeof (struct TALER_Amount)); + memcpy (denom->currency, + cur, + slen); + return GNUNET_OK; } /** - * FIXME + * Set @a a to "invalid". + * + * @param a amount to set to invalid */ -struct TALER_Amount -TALER_amount_ntoh (const struct TALER_AmountNBO dn) +static void +invalidate (struct TALER_Amount *a) { - struct TALER_Amount d; - d.value = ntohl (dn.value); - d.fraction = ntohl (dn.fraction); - memcpy (d.currency, dn.currency, TALER_CURRENCY_LEN); + memset (a, + 0, + sizeof (struct TALER_Amount)); +} + - return d; +/** + * Test if @a a is valid + * + * @param a amount to test + * @return #GNUNET_YES if valid, + * #GNUNET_NO if invalid + */ +static int +test_valid (const struct TALER_Amount *a) +{ + return ('\0' != a->currency[0]); +} + + +/** + * Test if @a a1 and @a a2 are the same currency. + * + * @param a1 amount to test + * @param a2 amount to test + * @return #GNUNET_YES if @a a1 and @a a2 are the same currency + * #GNUNET_NO if the currencies are different, + * #GNUNET_SYSERR if either amount is invalid + */ +int +TALER_amount_cmp_currency (const struct TALER_Amount *a1, + const struct TALER_Amount *a2) +{ + if ( (GNUNET_NO == test_valid (a1)) || + (GNUNET_NO == test_valid (a2)) ) + return GNUNET_SYSERR; + if (0 == strcmp (a1->currency, + a2->currency)) + return GNUNET_YES; + return GNUNET_NO; } /** - * Compare the value/fraction of two amounts. Does not compare the currency, - * i.e. comparing amounts with the same value and fraction but different - * currency would return 0. + * Compare the value/fraction of two amounts. Does not compare the currency. + * Comparing amounts of different currencies will cause the program to abort(). + * If unsure, check with #TALER_amount_cmp_currency() first to be sure that + * the currencies of the two amounts are identical. * * @param a1 first amount * @param a2 second amount * @return result of the comparison */ int -TALER_amount_cmp (struct TALER_Amount a1, - struct TALER_Amount a2) +TALER_amount_cmp (const struct TALER_Amount *a1, + const struct TALER_Amount *a2) { - a1 = TALER_amount_normalize (a1); - a2 = TALER_amount_normalize (a2); - if (a1.value == a2.value) + struct TALER_Amount n1; + struct TALER_Amount n2; + + GNUNET_assert (GNUNET_YES == + TALER_amount_cmp_currency (a1, a2)); + n1 = *a1; + n2 = *a2; + TALER_amount_normalize (&n1); + TALER_amount_normalize (&n2); + if (n1.value == n2.value) { - if (a1.fraction < a2.fraction) + if (n1.fraction < n2.fraction) return -1; - if (a1.fraction > a2.fraction) + if (n1.fraction > n2.fraction) return 1; return 0; } - if (a1.value < a2.value) + if (n1.value < n2.value) return -1; return 1; } @@ -214,109 +305,142 @@ TALER_amount_cmp (struct TALER_Amount a1, /** * Perform saturating subtraction of amounts. * + * @param diff where to store (@a a1 - @a a2), or invalid if @a a2 > @a a1 * @param a1 amount to subtract from * @param a2 amount to subtract - * @return (a1-a2) or 0 if a2>=a1 + * @return #GNUNET_OK if the subtraction worked, + * #GNUNET_NO if @a a1 = @a a2 + * #GNUNET_SYSERR if @a a2 > @a a1 or currencies are incompatible; + * @a diff is set to invalid */ -struct TALER_Amount -TALER_amount_subtract (struct TALER_Amount a1, - struct TALER_Amount a2) +int +TALER_amount_subtract (struct TALER_Amount *diff, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2) { - a1 = TALER_amount_normalize (a1); - a2 = TALER_amount_normalize (a2); + struct TALER_Amount n1; + struct TALER_Amount n2; - if (a1.value < a2.value) + if (GNUNET_YES != + TALER_amount_cmp_currency (a1, a2)) { - a1.value = 0; - a1.fraction = 0; - return a1; + invalidate (diff); + return GNUNET_SYSERR; } + n1 = *a1; + n2 = *a2; + TALER_amount_normalize (&n1); + TALER_amount_normalize (&n2); - if (a1.fraction < a2.fraction) + if (n1.fraction < n2.fraction) { - if (0 == a1.value) + if (0 == n1.value) { - a1.fraction = 0; - return a1; + invalidate (diff); + return GNUNET_SYSERR; } - a1.fraction += AMOUNT_FRAC_BASE; - a1.value -= 1; + n1.fraction += TALER_AMOUNT_FRAC_BASE; + n1.value--; } - - a1.fraction -= a2.fraction; - a1.value -= a2.value; - - return a1; + if (n1.value < n2.value) + { + invalidate (diff); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (a1->currency, + diff)); + GNUNET_assert (n1.fraction >= n2.fraction); + diff->fraction = n1.fraction - n2.fraction; + GNUNET_assert (n1.value >= n2.value); + diff->value = n1.value - n2.value; + if ( (0 == diff->fraction) && + (0 == diff->value) ) + return GNUNET_NO; + return GNUNET_OK; } /** - * Perform saturating addition of amounts. + * Perform addition of amounts. * + * @param sum where to store @a a1 + @a a2, set to "invalid" on overflow * @param a1 first amount to add * @param a2 second amount to add - * @return sum of a1 and a2 + * @return #GNUNET_OK if the addition worked, + * #GNUNET_SYSERR on overflow */ -struct TALER_Amount -TALER_amount_add (struct TALER_Amount a1, - struct TALER_Amount a2) +int +TALER_amount_add (struct TALER_Amount *sum, + const struct TALER_Amount *a1, + const struct TALER_Amount *a2) { - a1 = TALER_amount_normalize (a1); - a2 = TALER_amount_normalize (a2); - - a1.value += a2.value; - a1.fraction += a2.fraction; + struct TALER_Amount n1; + struct TALER_Amount n2; - if (0 == a1.currency[0]) + if (GNUNET_YES != + TALER_amount_cmp_currency (a1, a2)) { - memcpy (a2.currency, - a1.currency, - TALER_CURRENCY_LEN); - } - - if (0 == a2.currency[0]) - { - memcpy (a1.currency, - a2.currency, - TALER_CURRENCY_LEN); + invalidate (sum); + return GNUNET_SYSERR; } - - if ( (0 != a1.currency[0]) && - (0 != memcmp (a1.currency, - a2.currency, - TALER_CURRENCY_LEN)) ) + n1 = *a1; + n2 = *a2; + TALER_amount_normalize (&n1); + TALER_amount_normalize (&n2); + + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (a1->currency, + sum)); + sum->value = n1.value + n2.value; + if (sum->value < n1.value) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "adding mismatching currencies\n"); + /* integer overflow */ + invalidate (sum); + return GNUNET_SYSERR; } - - if (a1.value < a2.value) + sum->fraction = n1.fraction + n2.fraction; + if (GNUNET_SYSERR == + TALER_amount_normalize (sum)) { - a1.value = UINT32_MAX; - a2.value = UINT32_MAX; - return a1; + /* integer overflow via carry from fraction */ + invalidate (sum); + return GNUNET_SYSERR; } - - return TALER_amount_normalize (a1); + return GNUNET_OK; } /** * Normalize the given amount. * - * @param amout amount to normalize - * @return normalized amount + * @param amount amount to normalize + * @return #GNUNET_OK if normalization worked + * #GNUNET_NO if value was already normalized + * #GNUNET_SYSERR if value was invalid or could not be normalized */ -struct TALER_Amount -TALER_amount_normalize (struct TALER_Amount amount) +int +TALER_amount_normalize (struct TALER_Amount *amount) { - while ( (amount.value != UINT32_MAX) && - (amount.fraction >= AMOUNT_FRAC_BASE) ) + int ret; + + if (GNUNET_YES != test_valid (amount)) + return GNUNET_SYSERR; + ret = GNUNET_NO; + while ( (amount->value != UINT64_MAX) && + (amount->fraction >= TALER_AMOUNT_FRAC_BASE) ) { - amount.fraction -= AMOUNT_FRAC_BASE; - amount.value += 1; + amount->fraction -= TALER_AMOUNT_FRAC_BASE; + amount->value++; + ret = GNUNET_OK; } - return amount; + if (amount->fraction >= TALER_AMOUNT_FRAC_BASE) + { + /* failed to normalize, adding up fractions caused + main value to overflow! */ + return GNUNET_SYSERR; + } + return ret; } @@ -327,40 +451,40 @@ TALER_amount_normalize (struct TALER_Amount amount) * @return freshly allocated string representation */ char * -TALER_amount_to_string (struct TALER_Amount amount) +TALER_amount_to_string (const struct TALER_Amount *amount) { - char tail[AMOUNT_FRAC_LEN + 1] = { 0 }; - char curr[TALER_CURRENCY_LEN + 1] = { 0 }; - char *result = NULL; - int len; - - memcpy (curr, amount.currency, TALER_CURRENCY_LEN); - - amount = TALER_amount_normalize (amount); - if (0 != amount.fraction) + char *result; + uint32_t n; + char tail[TALER_AMOUNT_FRAC_LEN + 1]; + unsigned int i; + struct TALER_Amount norm; + + if (GNUNET_YES != test_valid (amount)) + return NULL; + norm = *amount; + GNUNET_break (GNUNET_SYSERR != + TALER_amount_normalize (&norm)); + if (0 != (n = norm.fraction)) { - unsigned int i; - uint32_t n = amount.fraction; - for (i = 0; (i < AMOUNT_FRAC_LEN) && (n != 0); i++) + for (i = 0; (i < TALER_AMOUNT_FRAC_LEN) && (0 != n); i++) { - tail[i] = '0' + (n / (AMOUNT_FRAC_BASE / 10)); - n = (n * 10) % (AMOUNT_FRAC_BASE); + tail[i] = '0' + (n / (TALER_AMOUNT_FRAC_BASE / 10)); + n = (n * 10) % (TALER_AMOUNT_FRAC_BASE); } - tail[i] = 0; - len = GNUNET_asprintf (&result, - "%s:%lu.%s", - curr, - (unsigned long) amount.value, - tail); + tail[i] = '\0'; + GNUNET_asprintf (&result, + "%s:%llu.%s", + norm.currency, + (unsigned long long) norm.value, + tail); } else { - len = GNUNET_asprintf (&result, - "%s:%lu", - curr, - (unsigned long) amount.value); + GNUNET_asprintf (&result, + "%s:%llu", + norm.currency, + (unsigned long long) norm.value); } - GNUNET_assert (len > 0); return result; } diff --git a/src/util/json.c b/src/util/json.c index 84fac4c9..7390eb47 100644 --- a/src/util/json.c +++ b/src/util/json.c @@ -48,14 +48,25 @@ * @return a json object describing the amount */ json_t * -TALER_JSON_from_amount (struct TALER_Amount amount) +TALER_JSON_from_amount (const struct TALER_Amount *amount) { json_t *j; - j = json_pack ("{s: s, s:I, s:I}", - "currency", amount.currency, - "value", (json_int_t) amount.value, - "fraction", (json_int_t) amount.fraction); + if ( (amount->value != (uint64_t) ((json_int_t) amount->value)) || + (0 > ((json_int_t) amount->value)) ) + { + /* Theoretically, json_int_t can be a 32-bit "long", or we might + have a 64-bit value which converted to a 63-bit signed long + long causes problems here. So we check. Note that depending + on the platform, the compiler may be able to statically tell + that at least the first check is always false. */ + GNUNET_break (0); + return NULL; + } + j = json_pack ("{s:s, s:I, s:I}", + "currency", amount->currency, + "value", (json_int_t) amount->value, + "fraction", (json_int_t) amount->fraction); GNUNET_assert (NULL != j); return j; } -- cgit v1.2.3 From 7d9a40327587ed99fb20f4c4a5669069c7a51e48 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 20 Mar 2015 23:51:28 +0100 Subject: first stab at establishing proper plugin API, main HTTP code compiles, other binaries FTBFS right now --- INSTALL | 12 +- configure.ac | 7 + src/include/taler_util.h | 12 + src/mint/Makefile.am | 21 +- src/mint/mint_db.c | 2241 --------------------------------- src/mint/mint_db.h | 976 --------------- src/mint/plugin.c | 183 +++ src/mint/plugin.h | 77 ++ src/mint/plugin_mintdb_postgres.c | 2316 +++++++++++++++++++++++++++++++++++ src/mint/taler-mint-dbinit.c | 2 +- src/mint/taler-mint-httpd.c | 13 +- src/mint/taler-mint-httpd_db.c | 324 +++-- src/mint/taler-mint-httpd_db.h | 2 +- src/mint/taler-mint-httpd_deposit.c | 2 +- src/mint/taler-mint-httpd_refresh.c | 2 +- src/mint/taler-mint-reservemod.c | 2 +- src/mint/taler_mintdb_plugin.h | 1002 +++++++++++++++ src/util/Makefile.am | 3 +- src/util/os_installation.c | 701 +++++++++++ 19 files changed, 4519 insertions(+), 3379 deletions(-) delete mode 100644 src/mint/mint_db.c delete mode 100644 src/mint/mint_db.h create mode 100644 src/mint/plugin.c create mode 100644 src/mint/plugin.h create mode 100644 src/mint/plugin_mintdb_postgres.c create mode 100644 src/mint/taler_mintdb_plugin.h create mode 100644 src/util/os_installation.c (limited to 'src/mint/mint_db.h') diff --git a/INSTALL b/INSTALL index a1e89e18..20998407 100644 --- a/INSTALL +++ b/INSTALL @@ -1,7 +1,7 @@ Installation Instructions ************************* -Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, @@ -12,8 +12,8 @@ without warranty of any kind. Basic Installation ================== - Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following + Briefly, the shell command `./configure && make && make install' +should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. Some packages provide this `INSTALL' file but do not implement all of the features documented @@ -309,9 +309,10 @@ causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== @@ -367,4 +368,3 @@ operates. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. - diff --git a/configure.ac b/configure.ac index 5835cb63..f69bcefd 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,13 @@ AS_IF([test $libgnunetutil != 1], *** ]])]) +TALER_LIB_LDFLAGS="-export-dynamic -no-undefined" +TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined" + +AC_SUBST(TALER_LIB_LDFLAGS) +AC_SUBST(TALER_PLUGIN_LDFLAGS) + + # check for libmicrohttpd microhttpd=0 AC_MSG_CHECKING([for microhttpd]) diff --git a/src/include/taler_util.h b/src/include/taler_util.h index 3569bd78..e4658398 100644 --- a/src/include/taler_util.h +++ b/src/include/taler_util.h @@ -99,4 +99,16 @@ TALER_config_get_denom (struct GNUNET_CONFIGURATION_Handle *cfg, struct TALER_Amount *denom); +/** + * Get the path to a specific Taler installation directory or, with + * #GNUNET_OS_IPK_SELF_PREFIX, the current running apps installation + * directory. + * + * @param dirkind what kind of directory is desired? + * @return a pointer to the dir path (to be freed by the caller) + */ +char * +TALER_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind); + + #endif diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am index 2436d7f3..c4f99af6 100644 --- a/src/mint/Makefile.am +++ b/src/mint/Makefile.am @@ -1,18 +1,31 @@ # This Makefile.am is in the public domain AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_plugin_mintdb_postgres.la + +libtaler_plugin_mintdb_postgres_la_SOURCES = \ + plugin_mintdb_postgres.c +libtaler_plugin_mintdb_postgres_la_LIBADD = \ + $(LTLIBINTL) +libtaler_plugin_mintdb_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + -lpq \ + -lgnunetutil + lib_LTLIBRARIES = \ libtalermint_common.la -# Note: mint_db.c should become a plugin soon! (#3608) libtalermint_common_la_SOURCES = \ key_io.c key_io.h \ - mint_db.c + plugin.c plugin.h \ + taler_mintdb_plugin.h libtalermint_common_la_LIBADD = \ $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetutil \ - -lpq + -lgnunetutil libtalermint_common_la_LDFLAGS = \ $(POSTGRESQL_LDFLAGS) \ diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c deleted file mode 100644 index 6832cdac..00000000 --- a/src/mint/mint_db.c +++ /dev/null @@ -1,2241 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see -*/ - -/** - * @file mint_db.c - * @brief Low-level (statement-level) database access for the mint - * @author Florian Dold - * @author Christian Grothoff - * @author Sree Harsha Totakura - * - * TODO: - * - The mint_db.h-API should ideally be what we need to port - * when using other databases; so here we should enable - * alternative implementations by returning - * a more opaque DB handle. - */ -#include "platform.h" -#include "db_pq.h" -#include "taler_signatures.h" -#include "taler-mint-httpd_responses.h" -#include "mint_db.h" -#include - - -/** - * Thread-local database connection. - * Contains a pointer to PGconn or NULL. - */ -static pthread_key_t db_conn_threadlocal; - - -#define QUERY_ERR(result) \ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) - -/** - * Database connection string, as read from - * the configuration. - */ -static char *TALER_MINT_db_connection_cfg_str; - -#define BREAK_DB_ERR(result) do { \ - GNUNET_break(0); \ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ - } while (0) - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -#define SQLEXEC_(conn, sql, result) \ - do { \ - result = PQexec (conn, sql); \ - if (PGRES_COMMAND_OK != PQresultStatus (result)) \ - { \ - BREAK_DB_ERR (result); \ - PQclear (result); result = NULL; \ - goto SQLEXEC_fail; \ - } \ - PQclear (result); result = NULL; \ - } while (0) - -/** - * This the length of the currency strings (without 0-termination) we use. Note - * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the - * DB only needs to store 3 bytes instead of 8 bytes. - */ -#define TALER_DB_CURRENCY_LEN 3 - -/** - * Set the given connection to use a temporary schema - * - * @param db the database connection - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error - */ -static int -set_temporary_schema (PGconn *db) -{ - PGresult *result; - - SQLEXEC_(db, - "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" - "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", - result); - return GNUNET_OK; - SQLEXEC_fail: - return GNUNET_SYSERR; -} - - -/** - * Drop the temporary taler schema. This is only useful for testcases - * - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_drop_temporary (PGconn *db) -{ - PGresult *result; - - SQLEXEC_ (db, - "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", - result); - return GNUNET_OK; - SQLEXEC_fail: - return GNUNET_SYSERR; -} - - -/** - * Create the necessary tables if they are not present - * - * @param temporary should we use a temporary schema - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_create_tables (int temporary) -{ - PGresult *result; - PGconn *conn; - - result = NULL; - conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); - if (CONNECTION_OK != PQstatus (conn)) - { - LOG_ERROR ("Database connection failed: %s\n", - PQerrorMessage (conn)); - GNUNET_break (0); - return GNUNET_SYSERR; - } - if ((GNUNET_YES == temporary) - && (GNUNET_SYSERR == set_temporary_schema (conn))) - { - PQfinish (conn); - return GNUNET_SYSERR; - } -#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); - /* reserves table is for summarization of a reserve. It is updated when new - funds are added and existing funds are withdrawn */ - SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" - "(" - " reserve_pub BYTEA PRIMARY KEY" - ",current_balance_value INT4 NOT NULL" - ",current_balance_fraction INT4 NOT NULL" - ",balance_currency VARCHAR(4) NOT NULL" - ",expiration_date INT8 NOT NULL" - ")"); - /* reserves_in table collects the transactions which transfer funds into the - reserve. The amount and expiration date for the corresponding reserve are - updated when new transfer funds are added. The rows of this table - correspond to each incoming transaction. */ - SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" - "(" - " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" - ",balance_value INT4 NOT NULL" - ",balance_fraction INT4 NOT NULL" - ",balance_currency VARCHAR(4) NOT NULL" - ",expiration_date INT8 NOT NULL" - ");"); - /* Create an index on the foreign key as it is not created automatically by PSQL */ - SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" - " ON reserves_in (reserve_pub);"); - SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" - "(" - "blind_ev BYTEA PRIMARY KEY" - ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ - ",denom_sig BYTEA NOT NULL" - ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" - ",reserve_sig BYTEA NOT NULL" - ");"); - SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" - " collectable_blindcoins (reserve_pub)"); - SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " - "(" - " coin_pub BYTEA NOT NULL PRIMARY KEY" - ",denom_pub BYTEA NOT NULL" - ",denom_sig BYTEA NOT NULL" - ",expended_value INT4 NOT NULL" - ",expended_fraction INT4 NOT NULL" - ",expended_currency VARCHAR(4) NOT NULL" - ",refresh_session_pub BYTEA" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " - "(" - " session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)" - ",session_melt_sig BYTEA" - ",session_commit_sig BYTEA" - ",noreveal_index INT2 NOT NULL" - // non-zero if all reveals were ok - // and the new coin signatures are ready - ",reveal_ok BOOLEAN NOT NULL DEFAULT false" - ") "); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " - "( " - " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" - ",newcoin_index INT2 NOT NULL " - ",denom_pub BYTEA NOT NULL " - ",PRIMARY KEY (session_pub, newcoin_index)" - ") "); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" - "(" - " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" - ",transfer_pub BYTEA NOT NULL" - ",link_secret_enc BYTEA NOT NULL" - // index of the old coin in the customer's request - ",oldcoin_index INT2 NOT NULL" - // index for cut and choose, - // ranges from 0 to kappa-1 - ",cnc_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" - "(" - " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " - ",link_vector_enc BYTEA NOT NULL" - // index of the new coin in the customer's request - ",newcoin_index INT2 NOT NULL" - // index for cut and choose, - ",cnc_index INT2 NOT NULL" - ",coin_ev BYTEA NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" - "(" - " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " - ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " - ",denom_pub BYTEA NOT NULL " - ",oldcoin_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" - "(" - " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " - ",ev_sig BYTEA NOT NULL" - ",newcoin_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " - "( " - " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" - ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ - ",denom_sig BYTEA NOT NULL" - ",transaction_id INT8 NOT NULL" - ",amount_currency VARCHAR(4) NOT NULL" - ",amount_value INT4 NOT NULL" - ",amount_fraction INT4 NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" - ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=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" - ")"); -#undef SQLEXEC - PQfinish (conn); - return GNUNET_OK; - - SQLEXEC_fail: - PQfinish (conn); - return GNUNET_SYSERR; -} - - -/** - * Setup prepared statements. - * - * @param db_conn connection handle to initialize - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ -int -TALER_MINT_DB_prepare (PGconn *db_conn) -{ - PGresult *result; - -#define PREPARE(name, sql, ...) \ - do { \ - result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ - if (PGRES_COMMAND_OK != PQresultStatus (result)) \ - { \ - BREAK_DB_ERR (result); \ - PQclear (result); result = NULL; \ - return GNUNET_SYSERR; \ - } \ - PQclear (result); result = NULL; \ - } while (0); - - PREPARE ("get_reserve", - "SELECT " - "current_balance_value" - ",current_balance_fraction" - ",balance_currency " - ",expiration_date " - "FROM reserves " - "WHERE reserve_pub=$1 " - "LIMIT 1; ", - 1, NULL); - PREPARE ("create_reserve", - "INSERT INTO reserves (" - " reserve_pub," - " current_balance_value," - " current_balance_fraction," - " balance_currency," - " expiration_date) VALUES (" - "$1, $2, $3, $4, $5);", - 5, NULL); - PREPARE ("update_reserve", - "UPDATE reserves " - "SET" - " current_balance_value=$2 " - ",current_balance_fraction=$3 " - ",expiration_date=$4 " - "WHERE reserve_pub=$1 ", - 4, NULL); - PREPARE ("create_reserves_in_transaction", - "INSERT INTO reserves_in (" - " reserve_pub," - " balance_value," - " balance_fraction," - " balance_currency," - " expiration_date) VALUES (" - " $1, $2, $3, $4, $5);", - 5, NULL); - PREPARE ("get_reserves_in_transactions", - "SELECT" - " balance_value" - ",balance_fraction" - ",balance_currency" - ",expiration_date" - " FROM reserves_in WHERE reserve_pub=$1", - 1, NULL); - PREPARE ("insert_collectable_blindcoin", - "INSERT INTO collectable_blindcoins ( " - " blind_ev" - ",denom_pub, denom_sig" - ",reserve_pub, reserve_sig) " - "VALUES ($1, $2, $3, $4, $5)", - 5, NULL); - PREPARE ("get_collectable_blindcoin", - "SELECT " - " denom_pub, denom_sig" - ",reserve_sig, reserve_pub " - "FROM collectable_blindcoins " - "WHERE blind_ev = $1", - 1, NULL); - PREPARE ("get_reserves_blindcoins", - "select" - " blind_ev" - ",denom_pub, denom_sig" - ",reserve_sig" - " FROM collectable_blindcoins" - " WHERE reserve_pub=$1;", - 1, NULL); - - /* FIXME: does it make sense to store these computed values in the DB? */ -#if 0 - PREPARE ("get_refresh_session", - "SELECT " - " (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins " - ",(SELECT count(*) FROM refresh_blind_session_keys " - " WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins " - ",(SELECT count(*) FROM refresh_blind_session_keys " - " WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa " - ",noreveal_index" - ",session_commit_sig " - ",reveal_ok " - "FROM refresh_sessions " - "WHERE session_pub = $1", - 1, NULL); -#endif - - PREPARE ("get_known_coin", - "SELECT " - " coin_pub, denom_pub, denom_sig " - ",expended_value, expended_fraction, expended_currency " - ",refresh_session_pub " - "FROM known_coins " - "WHERE coin_pub = $1", - 1, NULL); - PREPARE ("update_known_coin", - "UPDATE known_coins " - "SET " - " denom_pub = $2 " - ",denom_sig = $3 " - ",expended_value = $4 " - ",expended_fraction = $5 " - ",expended_currency = $6 " - ",refresh_session_pub = $7 " - "WHERE " - " coin_pub = $1 ", - 7, NULL); - PREPARE ("insert_known_coin", - "INSERT INTO known_coins (" - " coin_pub" - ",denom_pub" - ",denom_sig" - ",expended_value" - ",expended_fraction" - ",expended_currency" - ",refresh_session_pub" - ")" - "VALUES ($1,$2,$3,$4,$5,$6,$7)", - 7, NULL); - PREPARE ("get_refresh_commit_link", - "SELECT " - " transfer_pub " - ",link_secret_enc " - "FROM refresh_commit_link " - "WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3", - 3, NULL); - PREPARE ("get_refresh_commit_coin", - "SELECT " - " link_vector_enc " - ",coin_ev " - "FROM refresh_commit_coin " - "WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3", - 3, NULL); - PREPARE ("insert_refresh_order", - "INSERT INTO refresh_order ( " - " newcoin_index " - ",session_pub " - ",denom_pub " - ") " - "VALUES ($1, $2, $3) ", - 3, NULL); - PREPARE ("insert_refresh_melt", - "INSERT INTO refresh_melt ( " - " session_pub " - ",oldcoin_index " - ",coin_pub " - ",denom_pub " - ") " - "VALUES ($1, $2, $3, $4) ", - 3, NULL); - PREPARE ("get_refresh_order", - "SELECT denom_pub " - "FROM refresh_order " - "WHERE session_pub = $1 AND newcoin_index = $2", - 2, NULL); - PREPARE ("get_refresh_collectable", - "SELECT ev_sig " - "FROM refresh_collectable " - "WHERE session_pub = $1 AND newcoin_index = $2", - 2, NULL); - PREPARE ("get_refresh_melt", - "SELECT coin_pub " - "FROM refresh_melt " - "WHERE session_pub = $1 AND oldcoin_index = $2", - 2, NULL); - PREPARE ("insert_refresh_session", - "INSERT INTO refresh_sessions ( " - " session_pub " - ",noreveal_index " - ") " - "VALUES ($1, $2) ", - 2, NULL); - PREPARE ("insert_refresh_commit_link", - "INSERT INTO refresh_commit_link ( " - " session_pub " - ",transfer_pub " - ",cnc_index " - ",oldcoin_index " - ",link_secret_enc " - ") " - "VALUES ($1, $2, $3, $4, $5) ", - 5, NULL); - PREPARE ("insert_refresh_commit_coin", - "INSERT INTO refresh_commit_coin ( " - " session_pub " - ",coin_ev " - ",cnc_index " - ",newcoin_index " - ",link_vector_enc " - ") " - "VALUES ($1, $2, $3, $4, $5) ", - 5, NULL); - PREPARE ("insert_refresh_collectable", - "INSERT INTO refresh_collectable ( " - " session_pub " - ",newcoin_index " - ",ev_sig " - ") " - "VALUES ($1, $2, $3) ", - 3, NULL); - PREPARE ("set_reveal_ok", - "UPDATE refresh_sessions " - "SET reveal_ok = TRUE " - "WHERE session_pub = $1 ", - 1, NULL); - PREPARE ("get_link", - "SELECT link_vector_enc, ro.denom_pub, ev_sig " - "FROM refresh_melt rm " - " JOIN refresh_order ro USING (session_pub) " - " JOIN refresh_commit_coin rcc USING (session_pub) " - " JOIN refresh_sessions rs USING (session_pub) " - " JOIN refresh_collectable rc USING (session_pub) " - "WHERE rm.coin_pub = $1 " - "AND ro.newcoin_index = rcc.newcoin_index " - "AND ro.newcoin_index = rc.newcoin_index " - "AND rcc.cnc_index = rs.noreveal_index % ( " - " SELECT count(*) FROM refresh_commit_coin rcc2 " - " WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub " - " ) ", - 1, NULL); - PREPARE ("get_transfer", - "SELECT transfer_pub, link_secret_enc " - "FROM refresh_melt rm " - " JOIN refresh_commit_link rcl USING (session_pub) " - " JOIN refresh_sessions rs USING (session_pub) " - "WHERE rm.coin_pub = $1 " - "AND rm.oldcoin_index = rcl.oldcoin_index " - "AND rcl.cnc_index = rs.noreveal_index % ( " - " SELECT count(*) FROM refresh_commit_coin rcc2 " - " WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub " - " ) ", - 1, NULL); - PREPARE ("insert_deposit", - "INSERT INTO deposits (" - "coin_pub," - "denom_pub," - "denom_sig," - "transaction_id," - "amount_value," - "amount_fraction," - "amount_currency," - "merchant_pub," - "h_contract," - "h_wire," - "coin_sig," - "wire" - ") VALUES (" - "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" - ")", - 12, NULL); - PREPARE ("get_deposit", - "SELECT " - "coin_pub," - "denom_pub," - "transaction_id," - "amount_value," - "amount_fraction," - "amount_currency," - "merchant_pub," - "h_contract," - "h_wire," - "coin_sig" - " FROM deposits WHERE (" - "(coin_pub = $1) AND" - "(transaction_id = $2) AND" - "(merchant_pub = $3)" - ")", - 3, NULL); - return GNUNET_OK; -#undef PREPARE -} - - -/** - * Close thread-local database connection when a thread is destroyed. - * - * @param closure we get from pthreads (the db handle) - */ -static void -db_conn_destroy (void *cls) -{ - PGconn *db_conn = cls; - - if (NULL != db_conn) - PQfinish (db_conn); -} - - -/** - * Initialize database subsystem. - * - * @param connection_cfg configuration to use to talk to DB - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_init (const char *connection_cfg) -{ - if (0 != pthread_key_create (&db_conn_threadlocal, - &db_conn_destroy)) - { - LOG_ERROR ("Cannnot create pthread key.\n"); - return GNUNET_SYSERR; - } - TALER_MINT_db_connection_cfg_str = GNUNET_strdup (connection_cfg); - return GNUNET_OK; -} - - -/** - * Get the thread-local database-handle. - * Connect to the db if the connection does not exist yet. - * - * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the - * database default one - * @return the database connection, or NULL on error - */ -PGconn * -TALER_MINT_DB_get_connection (int temporary) -{ - PGconn *db_conn; - - if (NULL != (db_conn = pthread_getspecific (db_conn_threadlocal))) - return db_conn; - db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str); - if (CONNECTION_OK != - PQstatus (db_conn)) - { - LOG_ERROR ("Database connection failed: %s\n", - PQerrorMessage (db_conn)); - GNUNET_break (0); - return NULL; - } - if ((GNUNET_YES == temporary) - && (GNUNET_SYSERR == set_temporary_schema(db_conn))) - { - GNUNET_break (0); - return NULL; - } - if (GNUNET_OK != - TALER_MINT_DB_prepare (db_conn)) - { - GNUNET_break (0); - return NULL; - } - if (0 != pthread_setspecific (db_conn_threadlocal, - db_conn)) - { - GNUNET_break (0); - return NULL; - } - return db_conn; -} - - -/** - * Start a transaction. - * - * @param db_conn the database connection - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_transaction (PGconn *db_conn) -{ - PGresult *result; - - result = PQexec (db_conn, - "BEGIN"); - if (PGRES_COMMAND_OK != - PQresultStatus (result)) - { - LOG_ERROR ("Failed to start transaction: %s\n", - PQresultErrorMessage (result)); - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Roll back the current transaction of a database connection. - * - * @param db_conn the database connection - * @return #GNUNET_OK on success - */ -void -TALER_MINT_DB_rollback (PGconn *db_conn) -{ - PGresult *result; - - result = PQexec (db_conn, - "ROLLBACK"); - GNUNET_break (PGRES_COMMAND_OK == - PQresultStatus (result)); - PQclear (result); -} - - -/** - * Commit the current transaction of a database connection. - * - * @param db_conn the database connection - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_commit (PGconn *db_conn) -{ - PGresult *result; - - result = PQexec (db_conn, - "COMMIT"); - if (PGRES_COMMAND_OK != - PQresultStatus (result)) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Get the summary of a reserve. - * - * @param db the database connection handle - * @param reserve the reserve data. The public key of the reserve should be set - * in this structure; it is used to query the database. The balance - * and expiration are then filled accordingly. - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_reserve_get (PGconn *db, - struct Reserve *reserve) -{ - PGresult *result; - uint64_t expiration_date_nbo; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(reserve->pub), - TALER_DB_QUERY_PARAM_END - }; - - if (NULL == reserve->pub) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - result = TALER_DB_exec_prepared (db, - "get_reserve", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("expiration_date", &expiration_date_nbo), - TALER_DB_RESULT_SPEC_END - }; - EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)); - EXITIF (GNUNET_OK != - TALER_DB_extract_amount (result, 0, - "current_balance_value", - "current_balance_fraction", - "balance_currency", - &reserve->balance)); - reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); - PQclear (result); - return GNUNET_OK; - - EXITIF_exit: - PQclear (result); - return GNUNET_SYSERR; -} - - -/** - * Updates a reserve with the data from the given reserve structure. - * - * @param db the database connection - * @param reserve the reserve structure whose data will be used to update the - * corresponding record in the database. - * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error - */ -int -reserves_update (PGconn *db, - struct Reserve *reserve) -{ - PGresult *result; - struct TALER_AmountNBO balance_nbo; - struct GNUNET_TIME_AbsoluteNBO expiry_nbo; - int ret; - - if ((NULL == reserve) || (NULL == reserve->pub)) - return GNUNET_SYSERR; - ret = GNUNET_OK; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (reserve->pub), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), - TALER_DB_QUERY_PARAM_END - }; - TALER_amount_hton (&balance_nbo, - &reserve->balance); - expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); - result = TALER_DB_exec_prepared (db, - "update_reserve", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - ret = GNUNET_SYSERR; - } - PQclear (result); - return ret; -} - - -/** - * Insert a incoming transaction into reserves. New reserves are also created - * through this function. - * - * @param db the database connection handle - * @param reserve the reserve structure. The public key of the reserve should - * be set here. Upon successful execution of this function, the - * balance and expiration of the reserve will be updated. - * @param balance the amount that has to be added to the reserve - * @param expiry the new expiration time for the reserve - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures - */ -int -TALER_MINT_DB_reserves_in_insert (PGconn *db, - struct Reserve *reserve, - const struct TALER_Amount *balance, - const struct GNUNET_TIME_Absolute expiry) -{ - struct TALER_AmountNBO balance_nbo; - struct GNUNET_TIME_AbsoluteNBO expiry_nbo; - PGresult *result; - int reserve_exists; - - result = NULL; - if (NULL == reserve) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != TALER_MINT_DB_transaction (db)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - reserve_exists = TALER_MINT_DB_reserve_get (db, reserve); - if (GNUNET_SYSERR == reserve_exists) - { - TALER_MINT_DB_rollback (db); - return GNUNET_SYSERR; - } - TALER_amount_hton (&balance_nbo, - balance); - expiry_nbo = GNUNET_TIME_absolute_hton (expiry); - if (GNUNET_NO == reserve_exists) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Reserve does not exist; creating a new one\n"); - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (reserve->pub), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_DB_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, - TALER_DB_CURRENCY_LEN), - TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db, - "create_reserve", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - goto rollback; - } - } - if (NULL != result) - PQclear (result); - result = NULL; - /* create new incoming transaction */ - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (reserve->pub), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_DB_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, - TALER_DB_CURRENCY_LEN), - TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db, - "create_reserves_in_transaction", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - goto rollback; - } - PQclear (result); - result = NULL; - if (GNUNET_NO == reserve_exists) - { - if (GNUNET_OK != TALER_MINT_DB_commit (db)) - return GNUNET_SYSERR; - reserve->balance = *balance; - reserve->expiry = expiry; - return GNUNET_OK; - } - /* Update reserve */ - struct Reserve updated_reserve; - updated_reserve.pub = reserve->pub; - - if (GNUNET_OK != - TALER_amount_add (&updated_reserve.balance, - &reserve->balance, - balance)) - { - return GNUNET_SYSERR; - } - updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); - if (GNUNET_OK != reserves_update (db, &updated_reserve)) - goto rollback; - if (GNUNET_OK != TALER_MINT_DB_commit (db)) - return GNUNET_SYSERR; - reserve->balance = updated_reserve.balance; - reserve->expiry = updated_reserve.expiry; - return GNUNET_OK; - - rollback: - PQclear (result); - TALER_MINT_DB_rollback (db); - return GNUNET_SYSERR; -} - - -/** - * Locate the response for a /withdraw request under the - * key of the hash of the blinded message. - * - * @param db_conn database connection to use - * @param h_blind hash of the blinded message - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -int -TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, - const struct GNUNET_HashCode *h_blind, - struct CollectableBlindcoin *collectable) -{ - PGresult *result; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (h_blind), - TALER_DB_QUERY_PARAM_END - }; - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - struct GNUNET_CRYPTO_rsa_Signature *denom_sig; - char *denom_pub_enc; - char *denom_sig_enc; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub = NULL; - denom_pub_enc = NULL; - denom_sig_enc = NULL; - result = TALER_DB_exec_prepared (db_conn, - "get_collectable_blindcoin", - params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == PQntuples (result)) - { - ret = GNUNET_NO; - goto cleanup; - } - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), - TALER_DB_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), - TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), - TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), - TALER_DB_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) - { - GNUNET_break (0); - goto cleanup; - } - denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, - denom_pub_enc_size); - denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, - denom_sig_enc_size); - if ((NULL == denom_pub) || (NULL == denom_sig)) - { - GNUNET_break (0); - goto cleanup; - } - collectable->denom_pub = denom_pub; - collectable->sig = denom_sig; - ret = GNUNET_YES; - - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - if (GNUNET_YES != ret) - { if (NULL != denom_pub) - GNUNET_CRYPTO_rsa_public_key_free (denom_pub); - if (NULL != denom_sig) - GNUNET_CRYPTO_rsa_signature_free (denom_sig); - } - return ret; -} - - -/** - * Store collectable bit coin under the corresponding - * hash of the blinded message. - * - * @param db_conn database connection to use - * @param h_blind hash of the blinded message - * @param withdraw amount by which the reserve will be withdrawn with this - * transaction - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -int -TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, - const struct GNUNET_HashCode *h_blind, - struct TALER_Amount withdraw, - const struct CollectableBlindcoin *collectable) -{ - PGresult *result; - struct Reserve reserve; - char *denom_pub_enc = NULL; - char *denom_sig_enc = NULL; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub_enc_size = - GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub, - &denom_pub_enc); - denom_sig_enc_size = - GNUNET_CRYPTO_rsa_signature_encode (collectable->sig, - &denom_sig_enc); - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (h_blind), - TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), - TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ - TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub), - TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig), - TALER_DB_QUERY_PARAM_END - }; - if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn)) - goto cleanup; - result = TALER_DB_exec_prepared (db_conn, - "insert_collectable_blindcoin", - params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto rollback; - } - reserve.pub = (struct GNUNET_CRYPTO_EddsaPublicKey *) - &collectable->reserve_pub; - if (GNUNET_OK != TALER_MINT_DB_reserve_get (db_conn, - &reserve)) - goto rollback; - if (GNUNET_SYSERR == - TALER_amount_subtract (&reserve.balance, - &reserve.balance, - &withdraw)) - goto rollback; - if (GNUNET_OK != reserves_update (db_conn, &reserve)) - goto rollback; - if (GNUNET_OK == TALER_MINT_DB_commit (db_conn)) - { - ret = GNUNET_OK; - goto cleanup; - } - - rollback: - TALER_MINT_DB_rollback(db_conn); - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - return ret; -} - - -/** - * Get all of the transaction history associated with the specified - * reserve. - * - * @param db_conn connection to use - * @param reserve_pub public key of the reserve - * @return known transaction history (NULL if reserve is unknown) - */ -struct ReserveHistory * -TALER_MINT_DB_get_reserve_history (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub) -{ - PGresult *result; - struct ReserveHistory *rh; - struct ReserveHistory *rh_head; - int rows; - int ret; - - result = NULL; - rh = NULL; - rh_head = NULL; - ret = GNUNET_SYSERR; - { - struct BankTransfer *bt; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (reserve_pub), - TALER_DB_QUERY_PARAM_END - }; - - result = TALER_DB_exec_prepared (db_conn, - "get_reserves_in_transactions", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == (rows = PQntuples (result))) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Asked to fetch history for an unknown reserve.\n"); - goto cleanup; - } - while (0 < rows) - { - bt = GNUNET_new (struct BankTransfer); - if (GNUNET_OK != TALER_DB_extract_amount (result, - --rows, - "balance_value", - "balance_fraction", - "balance_currency", - &bt->amount)) - { - GNUNET_free (bt); - GNUNET_break (0); - goto cleanup; - } - (void) memcpy (&bt->reserve_pub, reserve_pub, sizeof (bt->reserve_pub)); - if (NULL != rh_head) - { - rh_head->next = GNUNET_new (struct ReserveHistory); - rh_head = rh_head->next; - } - else - { - rh_head = GNUNET_new (struct ReserveHistory); - rh = rh_head; - } - rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; - rh_head->details.bank = bt; - } - } - PQclear (result); - result = NULL; - { - struct GNUNET_HashCode blind_ev; - struct GNUNET_CRYPTO_EddsaSignature reserve_sig; - struct CollectableBlindcoin *cbc; - char *denom_pub_enc; - char *denom_sig_enc; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (reserve_pub), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db_conn, - "get_reserves_blindcoins", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == (rows = PQntuples (result))) - { - ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */ - goto cleanup; - } - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC ("blind_ev", &blind_ev), - TALER_DB_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), - TALER_DB_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), - TALER_DB_RESULT_SPEC ("reserve_sig", &reserve_sig), - TALER_DB_RESULT_SPEC_END - }; - GNUNET_assert (NULL != rh); - GNUNET_assert (NULL != rh_head); - GNUNET_assert (NULL == rh_head->next); - while (0 < rows) - { - if (GNUNET_YES != TALER_DB_extract_result (result, rs, --rows)) - { - GNUNET_break (0); - goto cleanup; - } - cbc = GNUNET_new (struct CollectableBlindcoin); - cbc->sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, - denom_sig_enc_size); - GNUNET_free (denom_sig_enc); - denom_sig_enc = NULL; - cbc->denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, - denom_pub_enc_size); - GNUNET_free (denom_pub_enc); - denom_pub_enc = NULL; - if ((NULL == cbc->sig) || (NULL == cbc->denom_pub)) - { - if (NULL != cbc->sig) - GNUNET_CRYPTO_rsa_signature_free (cbc->sig); - if (NULL != cbc->denom_pub) - GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub); - GNUNET_free (cbc); - GNUNET_break (0); - goto cleanup; - } - (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); - (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); - (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); - rh_head->next = GNUNET_new (struct ReserveHistory); - rh_head = rh_head->next; - rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; - rh_head->details.withdraw = cbc; - } - } - ret = GNUNET_OK; - - cleanup: - if (NULL != result) - PQclear (result); - if (GNUNET_SYSERR == ret) - { - TALER_MINT_DB_free_reserve_history (rh); - rh = NULL; - } - return rh; -} - - -/** - * Free memory associated with the given reserve history. - * - * @param rh history to free. - */ -void -TALER_MINT_DB_free_reserve_history (struct ReserveHistory *rh) -{ - struct BankTransfer *bt; - struct CollectableBlindcoin *cbc; - struct ReserveHistory *backref; - - while (NULL != rh) - { - switch(rh->type) - { - case TALER_MINT_DB_RO_BANK_TO_MINT: - bt = rh->details.bank; - if (NULL != bt->wire) - json_decref ((json_t *) bt->wire); /* FIXME: avoid cast? */ - GNUNET_free (bt); - break; - case TALER_MINT_DB_RO_WITHDRAW_COIN: - cbc = rh->details.withdraw; - GNUNET_CRYPTO_rsa_signature_free (cbc->sig); - GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub); - GNUNET_free (cbc); - break; - } - backref = rh; - rh = rh->next; - GNUNET_free (backref); - } -} - - -/** - * Check if we have the specified deposit already in the database. - * - * @param db_conn database connection - * @param deposit deposit to search for - * @return #GNUNET_YES if we know this operation, - * #GNUNET_NO if this deposit is unknown to us - */ -int -TALER_MINT_DB_have_deposit (PGconn *db_conn, - const struct Deposit *deposit) -{ - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), - TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), - TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), - TALER_DB_QUERY_PARAM_END - }; - PGresult *result; - int ret; - - ret = GNUNET_SYSERR; - result = TALER_DB_exec_prepared (db_conn, - "get_deposit", - params); - if (PGRES_TUPLES_OK != - PQresultStatus (result)) - { - BREAK_DB_ERR (result); - goto cleanup; - } - - if (0 == PQntuples (result)) - { - ret = GNUNET_NO; - goto cleanup; - } - ret = GNUNET_YES; - - cleanup: - PQclear (result); - return ret; -} - - -/** - * Insert information about deposited coin into the - * database. - * - * @param db_conn connection to the database - * @param deposit deposit information to store - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_insert_deposit (PGconn *db_conn, - const struct Deposit *deposit) -{ - char *denom_pub_enc; - char *denom_sig_enc; - char *json_wire_enc; - PGresult *result; - struct TALER_AmountNBO amount_nbo; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub_enc_size = - GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub, - &denom_pub_enc); - denom_sig_enc_size = - GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig, - &denom_sig_enc); - json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); - TALER_amount_hton (&amount_nbo, - &deposit->amount); - struct TALER_DB_QueryParam params[]= { - TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), - TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), - TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), - TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), - TALER_DB_QUERY_PARAM_PTR (&amount_nbo.value), - TALER_DB_QUERY_PARAM_PTR (&amount_nbo.fraction), - TALER_DB_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, - TALER_CURRENCY_LEN - 1), - TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), - TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract), - TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire), - TALER_DB_QUERY_PARAM_PTR (&deposit->csig), - TALER_DB_QUERY_PARAM_PTR_SIZED (json_wire_enc, - strlen (json_wire_enc)), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db_conn, "insert_deposit", params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - goto cleanup; - } - ret = GNUNET_OK; - - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - GNUNET_free_non_null (json_wire_enc); - return ret; -} - - -/** - * Lookup refresh session data under the given public key. - * - * @param db_conn database handle to use - * @param refresh_session_pub public key to use for the lookup - * @param session[OUT] where to store the result - * @return #GNUNET_YES on success, - * #GNUNET_NO if not found, - * #GNUNET_SYSERR on DB failure - */ -int -TALER_MINT_DB_get_refresh_session (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - struct RefreshSession *session) -{ - // FIXME: check logic! - int res; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_session", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Query failed: %s\n", - PQresultErrorMessage (result)); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - return GNUNET_NO; - - GNUNET_assert (1 == PQntuples (result)); - - /* We're done if the caller is only interested in - * whether the session exists or not */ - - if (NULL == session) - return GNUNET_YES; - - memset (session, 0, sizeof (struct RefreshSession)); - - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("num_oldcoins", &session->num_oldcoins), - TALER_DB_RESULT_SPEC("num_newcoins", &session->num_newcoins), - TALER_DB_RESULT_SPEC("kappa", &session->kappa), - TALER_DB_RESULT_SPEC("noreveal_index", &session->noreveal_index), - TALER_DB_RESULT_SPEC_END - }; - - res = TALER_DB_extract_result (result, rs, 0); - - if (GNUNET_OK != res) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - session->num_oldcoins = ntohs (session->num_oldcoins); - session->num_newcoins = ntohs (session->num_newcoins); - session->kappa = ntohs (session->kappa); - session->noreveal_index = ntohs (session->noreveal_index); - - PQclear (result); - return GNUNET_YES; -} - - -/** - * Store new refresh session data under the given public key. - * - * @param db_conn database handle to use - * @param refresh_session_pub public key to use to locate the session - * @param session session data to store - * @return #GNUNET_YES on success, - * #GNUNET_SYSERR on DB failure - */ -int -TALER_MINT_DB_create_refresh_session (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - const struct RefreshSession *session) -{ - // FIXME: actually store session data! - uint16_t noreveal_index; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(session_pub), - TALER_DB_QUERY_PARAM_PTR(&noreveal_index), - TALER_DB_QUERY_PARAM_END - }; - - noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); - noreveal_index = htonl (noreveal_index); - - PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_session", params); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Store the given /refresh/melt request in the database. - * - * @param db_conn database connection - * @param session session key of the melt operation - * @param oldcoin_index index of the coin to store - * @param melt melt operation - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session, - uint16_t oldcoin_index, - const struct RefreshMelt *melt) -{ - // FIXME: check logic! - uint16_t oldcoin_index_nbo = htons (oldcoin_index); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub, - &buf); - { - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(session), - TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&melt->coin.coin_pub), - TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db_conn, "insert_refresh_melt", params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Get information about melted coin details from the database. - * - * @param db_conn database connection - * @param session session key of the melt operation - * @param oldcoin_index index of the coin to retrieve - * @param melt melt data to fill in - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session, - uint16_t oldcoin_index, - struct RefreshMelt *melt) -{ - // FIXME: check logic! - GNUNET_break (0); - return GNUNET_SYSERR; -} - - -/** - * Store in the database which coin(s) we want to create - * in a given refresh operation. - * - * @param db_conn database connection - * @param session_pub refresh session key - * @param newcoin_index index of the coin to generate - * @param denom_pub denomination of the coin to create - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index, - const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub) -{ - // FIXME: check logic - uint16_t newcoin_index_nbo = htons (newcoin_index); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pub, - &buf); - - { - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (&newcoin_index_nbo), - TALER_DB_QUERY_PARAM_PTR (session_pub), - TALER_DB_QUERY_PARAM_PTR_SIZED (buf, buf_size), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db_conn, - "insert_refresh_order", - params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Lookup in the database the @a newcoin_index coin that we want to - * create in the given refresh operation. - * - * @param db_conn database connection - * @param session_pub refresh session key - * @param newcoin_index index of the coin to generate - * @param denom_pub denomination of the coin to create - * @return NULL on error (not found or internal error) - */ -struct GNUNET_CRYPTO_rsa_PublicKey * -TALER_MINT_DB_get_refresh_order (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index) -{ - // FIXME: check logic - char *buf; - size_t buf_size; - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - uint16_t newcoin_index_nbo = htons (newcoin_index); - - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(session_pub), - TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_order", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return NULL; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - /* FIXME: may want to distinguish between different error cases! */ - return NULL; - } - GNUNET_assert (1 == PQntuples (result)); - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), - TALER_DB_RESULT_SPEC_END - }; - if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) - { - PQclear (result); - GNUNET_break (0); - return NULL; - } - PQclear (result); - denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size); - GNUNET_free (buf); - return denom_pub; -} - - - -/** - * Store information about the commitment of the - * given coin for the given refresh session in the database. - * - * @param db_conn database connection to use - * @param refresh_session_pub refresh session this commitment belongs to - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to refreshed (new) coins - * @param commit_coin coin commitment to store - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - const struct RefreshCommitCoin *commit_coin) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (i); - uint16_t newcoin_index_nbo = htons (j); - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), - TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coin->coin_ev, commit_coin->coin_ev_size), - TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coin->refresh_link->coin_priv_enc, - commit_coin->refresh_link->blinding_key_enc_size + - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_coin", params); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain information about the commitment of the - * given coin of the given refresh session from the database. - * - * @param db_conn database connection to use - * @param refresh_session_pub refresh session the commitment belongs to - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to refreshed (new) coins - * @param commit_coin[OUT] coin commitment to return - * @return #GNUNET_OK on success - * #GNUNET_NO if not found - * #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int cnc_index, - unsigned int newcoin_index, - struct RefreshCommitCoin *cc) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (cnc_index); - uint16_t newcoin_index_nbo = htons (newcoin_index); - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), - TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_DB_QUERY_PARAM_END - }; - char *c_buf; - size_t c_buf_size; - char *rl_buf; - size_t rl_buf_size; - struct TALER_RefreshLinkEncrypted *rl; - - PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_coin", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), - TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), - TALER_DB_RESULT_SPEC_END - }; - if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) - { - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - if (rl_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) - { - GNUNET_free (c_buf); - GNUNET_free (rl_buf); - return GNUNET_SYSERR; - } - rl = TALER_refresh_link_encrypted_decode (rl_buf, - rl_buf_size); - GNUNET_free (rl_buf); - cc->refresh_link = rl; - cc->coin_ev = c_buf; - cc->coin_ev_size = c_buf_size; - return GNUNET_YES; -} - - -/** - * Store the commitment to the given (encrypted) refresh link data - * for the given refresh session. - * - * @param db_conn database connection to use - * @param refresh_session_pub public key of the refresh session this - * commitment belongs with - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to melted (old) coins - * @param commit_link link information to store - * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success - */ -int -TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - const struct RefreshCommitLink *commit_link) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (i); - uint16_t oldcoin_index_nbo = htons (j); - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), - TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub), - TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, - "insert_refresh_commit_link", - params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain the commited (encrypted) refresh link data - * for the given refresh session. - * - * @param db_conn database connection to use - * @param refresh_session_pub public key of the refresh session this - * commitment belongs with - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to melted (old) coins - * @param cc[OUT] link information to return - * @return #GNUNET_SYSERR on internal error, - * #GNUNET_NO if commitment was not found - * #GNUNET_OK on success - */ -int -TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int cnc_index, - unsigned int oldcoin_index, - struct RefreshCommitLink *cc) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (cnc_index); - uint16_t oldcoin_index_nbo = htons (oldcoin_index); - - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), - TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, - "get_refresh_commit_link", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("transfer_pub", &cc->transfer_pub), - TALER_DB_RESULT_SPEC("link_secret_enc", &cc->shared_secret_enc), - TALER_DB_RESULT_SPEC_END - }; - - if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) - { - PQclear (result); - GNUNET_free (cc); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Insert signature of a new coin generated during refresh into - * the database indexed by the refresh session and the index - * of the coin. This data is later used should an old coin - * be used to try to obtain the private keys during "/refresh/link". - * - * @param db_conn database connection - * @param session_pub refresh session - * @param newcoin_index coin index - * @param ev_sig coin signature - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index, - const struct GNUNET_CRYPTO_rsa_Signature *ev_sig) -{ - // FIXME: check logic! - uint16_t newcoin_index_nbo = htons (newcoin_index); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig, - &buf); - { - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(session_pub), - TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size), - TALER_DB_QUERY_PARAM_END - }; - result = TALER_DB_exec_prepared (db_conn, - "insert_refresh_collectable", - params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain the link data of a coin, that is the encrypted link - * information, the denomination keys and the signatures. - * - * @param db_conn database connection - * @param coin_pub public key to use to retrieve linkage data - * @return all known link data for the coin - */ -struct LinkDataList * -TALER_db_get_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) -{ - // FIXME: check logic! - struct LinkDataList *ldl; - struct LinkDataList *pos; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(coin_pub), - TALER_DB_QUERY_PARAM_END - }; - PGresult *result = TALER_DB_exec_prepared (db_conn, "get_link", params); - - ldl = NULL; - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return NULL; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return NULL; - } - - - int i = 0; - - for (i = 0; i < PQntuples (result); i++) - { - struct TALER_RefreshLinkEncrypted *link_enc; - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - struct GNUNET_CRYPTO_rsa_Signature *sig; - char *ld_buf; - size_t ld_buf_size; - char *pk_buf; - size_t pk_buf_size; - char *sig_buf; - size_t sig_buf_size; - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), - TALER_DB_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), - TALER_DB_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), - TALER_DB_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_DB_extract_result (result, rs, i)) - { - PQclear (result); - GNUNET_break (0); - TALER_db_link_data_list_free (ldl); - return NULL; - } - if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) - { - PQclear (result); - GNUNET_free (pk_buf); - GNUNET_free (sig_buf); - GNUNET_free (ld_buf); - TALER_db_link_data_list_free (ldl); - return NULL; - } - // FIXME: use util API for this! - link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + - ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); - link_enc->blinding_key_enc = (const char *) &link_enc[1]; - link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); - memcpy (link_enc->coin_priv_enc, - ld_buf, - ld_buf_size); - - sig = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, - sig_buf_size); - denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, - pk_buf_size); - GNUNET_free (pk_buf); - GNUNET_free (sig_buf); - GNUNET_free (ld_buf); - if ( (NULL == sig) || - (NULL == denom_pub) ) - { - if (NULL != denom_pub) - GNUNET_CRYPTO_rsa_public_key_free (denom_pub); - if (NULL != sig) - GNUNET_CRYPTO_rsa_signature_free (sig); - GNUNET_free (link_enc); - GNUNET_break (0); - PQclear (result); - TALER_db_link_data_list_free (ldl); - return NULL; - } - pos = GNUNET_new (struct LinkDataList); - pos->next = ldl; - pos->link_data_enc = link_enc; - pos->denom_pub = denom_pub; - pos->ev_sig = sig; - ldl = pos; - } - return ldl; -} - - -/** - * Free memory of the link data list. - * - * @param ldl link data list to release - */ -void -TALER_db_link_data_list_free (struct LinkDataList *ldl) -{ - GNUNET_break (0); // FIXME -} - - -/** - * Obtain shared secret and transfer public key from the public key of - * the coin. This information and the link information returned by - * #TALER_db_get_link() enable the owner of an old coin to determine - * the private keys of the new coins after the melt. - * - * - * @param db_conn database connection - * @param coin_pub public key of the coin - * @param transfer_pub[OUT] public transfer key - * @param shared_secret_enc[OUT] set to shared secret - * @return #GNUNET_OK on success, - * #GNUNET_NO on failure (not found) - * #GNUNET_SYSERR on internal failure (database issue) - */ -int -TALER_db_get_transfer (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, - struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, - struct TALER_EncryptedLinkSecret *shared_secret_enc) -{ - // FIXME: check logic! - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR(coin_pub), - TALER_DB_QUERY_PARAM_END - }; - - PGresult *result = TALER_DB_exec_prepared (db_conn, "get_transfer", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - if (1 != PQntuples (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "got %d tuples for get_transfer\n", - PQntuples (result)); - GNUNET_break (0); - return GNUNET_SYSERR; - } - - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub), - TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc), - TALER_DB_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) - { - PQclear (result); - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - - - -/** - * Compile a list of all (historic) transactions performed - * with the given coin (/refresh/melt and /deposit operations). - * - * @param db_conn database connection - * @param coin_pub coin to investigate - * @return list of transactions, NULL if coin is fresh - */ -struct TALER_MINT_DB_TransactionList * -TALER_MINT_DB_get_coin_transactions (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) -{ - // FIXME: check logic! - GNUNET_break (0); // FIXME: implement! - return NULL; -} - - -/** - * Free linked list of transactions. - * - * @param list list to free - */ -void -TALER_MINT_DB_free_coin_transaction_list (struct TALER_MINT_DB_TransactionList *list) -{ - // FIXME: check logic! - GNUNET_break (0); -} - - -/* end of mint_db.c */ diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h deleted file mode 100644 index d11ee8fe..00000000 --- a/src/mint/mint_db.h +++ /dev/null @@ -1,976 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see -*/ -/** - * @file mint/mint_db.h - * @brief Low-level (statement-level) database access for the mint - * @author Florian Dold - * @author Christian Grothoff - */ -#ifndef MINT_DB_H -#define MINT_DB_H - -#include -#include -#include -#include "taler_util.h" - -#define TALER_TEMP_SCHEMA_NAME "taler_temporary" - -/** - * Initialize database subsystem. - * - * @param connection_cfg configuration for the DB - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_init (const char *connection_cfg); - - -/** - * Get the thread-local database-handle. - * Connect to the db if the connection does not exist yet. - * - * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the - * database default one - * @param the database connection, or NULL on error - */ -PGconn * -TALER_MINT_DB_get_connection (int temporary); - - -/** - * Drop the temporary taler schema. This is only useful for testcases - * - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_drop_temporary (PGconn *db); - - -/** - * Create the necessary tables if they are not present - * - * @param temporary should we use a temporary schema - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_create_tables (int temporary); - -/** - * Setup prepared statements. FIXME: should this be part of the API, - * or just internal to "TALER_MINT_DB_get_connection()"? - * - * @param db_conn connection handle to initialize - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ -int -TALER_MINT_DB_prepare (PGconn *db_conn); - - -/** - * Start a transaction. - * - * @param db_conn connection to use - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_transaction (PGconn *db_conn); - - -/** - * Commit a transaction. - * - * @param db_conn connection to use - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_commit (PGconn *db_conn); - - -/** - * Abort/rollback a transaction. - * - * @param db_conn connection to use - */ -void -TALER_MINT_DB_rollback (PGconn *db_conn); - - -/** - * Information we keep on bank transfer(s) that established a reserve. - */ -struct BankTransfer -{ - - /** - * Public key of the reserve that was filled. - */ - struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; - - /** - * Amount that was transferred to the mint. - */ - struct TALER_Amount amount; - - /** - * Detailed wire information about the transaction. - */ - const json_t *wire; - -}; - - -/* FIXME: add functions to add bank transfers to our DB - (and to test if we already did add one) (#3633/#3717) */ - -/** - * A summary of a Reserve - */ -struct Reserve -{ - /** - * The reserve's public key. This uniquely identifies the reserve - */ - struct GNUNET_CRYPTO_EddsaPublicKey *pub; - - /** - * The balance amount existing in the reserve - */ - struct TALER_Amount balance; - - /** - * The expiration date of this reserve - */ - struct GNUNET_TIME_Absolute expiry; -}; - - -/** - * Information we keep for a withdrawn coin to reproduce - * the /withdraw operation if needed, and to have proof - * that a reserve was drained by this amount. - */ -struct CollectableBlindcoin -{ - - /** - * Our signature over the (blinded) coin. - */ - struct GNUNET_CRYPTO_rsa_Signature *sig; - - /** - * Denomination key (which coin was generated). - */ - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - - /** - * Public key of the reserve that was drained. - */ - struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; - - /** - * Hash over the blinded message, needed to verify - * the @e reserve_sig. - */ - struct GNUNET_HashCode h_coin_envelope; - - /** - * Signature confirming the withdrawl, matching @e reserve_pub, - * @e denom_pub and @e h_coin_envelope. - */ - struct GNUNET_CRYPTO_EddsaSignature reserve_sig; -}; - - -/** - * Get the summary of a reserve. - * - * @param db the database connection handle - * @param reserve the reserve data. The public key of the reserve should be set - * in this structure; it is used to query the database. The balance - * and expiration are then filled accordingly. - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_DB_reserve_get (PGconn *db, - struct Reserve *reserve); - - -/** - * Insert a incoming transaction into reserves. New reserves are also created - * through this function. - * - * @param db the database connection handle - * @param reserve the reserve structure. The public key of the reserve should - * be set here. Upon successful execution of this function, the - * balance and expiration of the reserve will be updated. - * @param balance the amount that has to be added to the reserve - * @param expiry the new expiration time for the reserve - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures - */ -int -TALER_MINT_DB_reserves_in_insert (PGconn *db, - struct Reserve *reserve, - const struct TALER_Amount *balance, - const struct GNUNET_TIME_Absolute expiry); - - -/** - * Locate the response for a /withdraw request under the - * key of the hash of the blinded message. - * - * @param db_conn database connection to use - * @param h_blind hash of the blinded message - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -int -TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn, - const struct GNUNET_HashCode *h_blind, - struct CollectableBlindcoin *collectable); - - -/** - * Store collectable bit coin under the corresponding - * hash of the blinded message. - * - * @param db_conn database connection to use - * @param h_blind hash of the blinded message - * @param withdraw amount by which the reserve will be withdrawn with this - * transaction - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -int -TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn, - const struct GNUNET_HashCode *h_blind, - struct TALER_Amount withdraw, - const struct CollectableBlindcoin *collectable); - - - -/** - * Types of operations on a reserved. - */ -enum TALER_MINT_DB_ReserveOperation -{ - /** - * Money was deposited into the reserve via a bank transfer. - */ - TALER_MINT_DB_RO_BANK_TO_MINT = 0, - - /** - * A Coin was withdrawn from the reserve using /withdraw. - */ - TALER_MINT_DB_RO_WITHDRAW_COIN = 1 -}; - - -/** - * Reserve history as a linked list. Lists all of the transactions - * associated with this reserve (such as the bank transfers that - * established the reserve and all /withdraw operations we have done - * since). - */ -struct ReserveHistory -{ - - /** - * Next entry in the reserve history. - */ - struct ReserveHistory *next; - - /** - * Type of the event, determins @e details. - */ - enum TALER_MINT_DB_ReserveOperation type; - - /** - * Details of the operation, depending on @e type. - */ - union - { - - /** - * Details about a bank transfer to the mint. - */ - struct BankTransfer *bank; - - /** - * Details about a /withdraw operation. - */ - struct CollectableBlindcoin *withdraw; - - } details; - -}; - - -/** - * Get all of the transaction history associated with the specified - * reserve. - * - * @param db_conn connection to use - * @param reserve_pub public key of the reserve - * @return known transaction history (NULL if reserve is unknown) - */ -struct ReserveHistory * -TALER_MINT_DB_get_reserve_history (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub); - - -/** - * Free memory associated with the given reserve history. - * - * @param rh history to free. - */ -void -TALER_MINT_DB_free_reserve_history (struct ReserveHistory *rh); - - -/** - * Specification for a /deposit operation. - */ -struct Deposit -{ - /** - * Information about the coin that is being deposited. - */ - struct TALER_CoinPublicInfo coin; - - /** - * ECDSA signature affirming that the customer intends - * this coin to be deposited at the merchant identified - * by @e h_wire in relation to the contract identified - * by @e h_contract. - */ - struct GNUNET_CRYPTO_EcdsaSignature csig; - - /** - * Public key of the merchant. Enables later identification - * of the merchant in case of a need to rollback transactions. - */ - struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub; - - /** - * Hash over the contract between merchant and customer - * (remains unknown to the Mint). - */ - struct GNUNET_HashCode h_contract; - - /** - * Hash of the (canonical) representation of @e wire, used - * to check the signature on the request. Generated by - * the mint from the detailed wire data provided by the - * merchant. - */ - struct GNUNET_HashCode h_wire; - - /** - * Detailed wire information for executing the transaction. - */ - const json_t *wire; - - /** - * Merchant-generated transaction ID to detect duplicate - * transactions. - */ - uint64_t transaction_id; - - /** - * Fraction of the coin's remaining value to be deposited. - * The coin is identified by @e coin_pub. - */ - struct TALER_Amount amount; - -}; - - -/** - * Check if we have the specified deposit already in the database. - * - * @param db_conn database connection - * @param deposit deposit to search for - * @return #GNUNET_YES if we know this operation, - * #GNUNET_NO if this deposit is unknown to us, - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_have_deposit (PGconn *db_conn, - const struct Deposit *deposit); - - -/** - * Insert information about deposited coin into the - * database. - * - * @param db_conn connection to the database - * @param deposit deposit information to store - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_insert_deposit (PGconn *db_conn, - const struct Deposit *deposit); - - - -/** - * Global information for a refreshing session. Includes - * dimensions of the operation, security parameters and - * client signatures from "/refresh/melt" and "/refresh/commit". - */ -struct RefreshSession -{ - /** - * Signature over the commitments by the client, - * only valid if @e has_commit_sig is set. - */ - struct GNUNET_CRYPTO_EddsaSignature commit_sig; - - /** - * Hash over coins to melt and coins to create of the - * refresh session. - */ - struct GNUNET_HashCode session_hash; - - /** - * Signature over the melt by the client. - */ - struct GNUNET_CRYPTO_EddsaSignature melt_sig; - - /** - * Number of coins we are melting. - */ - uint16_t num_oldcoins; - - /** - * Number of new coins we are creating. - */ - uint16_t num_newcoins; - - /** - * Number of parallel operations we perform for the cut and choose. - * (must be greater or equal to three for security). 0 if not yet - * known. - */ - uint16_t kappa; - - /** - * Index (smaller @e kappa) which the mint has chosen to not - * have revealed during cut and choose. - */ - uint16_t noreveal_index; - -}; - - -/** - * Lookup refresh session data under the given public key. - * - * @param db_conn database handle to use - * @param refresh_session_pub public key to use for the lookup - * @param session[OUT] where to store the result - * @return #GNUNET_YES on success, - * #GNUNET_NO if not found, - * #GNUNET_SYSERR on DB failure - */ -int -TALER_MINT_DB_get_refresh_session (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - struct RefreshSession *session); - - -/** - * Store new refresh session data under the given public key. - * - * @param db_conn database handle to use - * @param refresh_session_pub public key to use to locate the session - * @param session session data to store - * @return #GNUNET_YES on success, - * #GNUNET_SYSERR on DB failure - */ -int -TALER_MINT_DB_create_refresh_session (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - const struct RefreshSession *session); - - -/** - * Specification for coin in a /refresh/melt operation. - */ -struct RefreshMelt -{ - /** - * Information about the coin that is being melted. - */ - struct TALER_CoinPublicInfo coin; - - /** - * Signature over the melting operation. - */ - struct GNUNET_CRYPTO_EcdsaSignature coin_sig; - - /** - * Which melting operation should the coin become a part of. - */ - struct GNUNET_HashCode melt_hash; - - /** - * How much value is being melted? - * This amount includes the fees, so the final amount contributed - * to the melt is this value minus the fee for melting the coin. - */ - struct TALER_Amount amount; - -}; - - -/** - * Store the given /refresh/melt request in the database. - * - * @param db_conn database connection - * @param session session key of the melt operation - * @param oldcoin_index index of the coin to store - * @param melt coin melt operation details to store - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session, - uint16_t oldcoin_index, - const struct RefreshMelt *melt); - - - -/** - * Get information about melted coin details from the database. - * - * @param db_conn database connection - * @param session session key of the melt operation - * @param oldcoin_index index of the coin to retrieve - * @param melt melt data to fill in - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_get_refresh_melt (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session, - uint16_t oldcoin_index, - struct RefreshMelt *melt); - - -/** - * Store in the database which coin(s) we want to create - * in a given refresh operation. - * - * @param db_conn database connection - * @param session_pub refresh session key - * @param newcoin_index index of the coin to generate - * @param denom_pub denomination of the coin to create - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_insert_refresh_order (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index, - const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub); - - -/** - * Lookup in the database the @a newcoin_index coin that we want to - * create in the given refresh operation. - * - * @param db_conn database connection - * @param session_pub refresh session key - * @param newcoin_index index of the coin to generate - * @param denom_pub denomination of the coin to create - * @return NULL on error (not found or internal error) - */ -struct GNUNET_CRYPTO_rsa_PublicKey * -TALER_MINT_DB_get_refresh_order (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index); - - -/** - * We have as many `struct RefreshCommitCoin` as there are new - * coins being created by the refresh (for each of the kappa - * sets). These are the coins we ask the mint to sign if the - * respective set is selected. - */ -struct RefreshCommitCoin -{ - - /** - * Encrypted data allowing those able to decrypt it to derive - * the private keys of the new coins created by the refresh. - */ - struct TALER_RefreshLinkEncrypted *refresh_link; - - /** - * Blinded message to be signed (in envelope), with @e coin_env_size bytes. - */ - char *coin_ev; - - /** - * Number of bytes in @e coin_ev. - */ - size_t coin_ev_size; - -}; - - -/** - * Store information about the commitment of the - * given coin for the given refresh session in the database. - * - * @param db_conn database connection to use - * @param refresh_session_pub refresh session this commitment belongs to - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to refreshed (new) coins - * @param commit_coin coin commitment to store - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - const struct RefreshCommitCoin *commit_coin); - - -/** - * Obtain information about the commitment of the - * given coin of the given refresh session from the database. - * - * @param db_conn database connection to use - * @param refresh_session_pub refresh session the commitment belongs to - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to refreshed (new) coins - * @param commit_coin[OUT] coin commitment to return - * @return #GNUNET_OK on success - * #GNUNET_NO if not found - * #GNUNET_SYSERR on error - */ -int -TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - struct RefreshCommitCoin *commit_coin); - - -/** - * For each (old) coin being melted, we have a `struct - * RefreshCommitLink` that allows the user to find the shared secret - * to decrypt the respective refresh links for the new coins in the - * `struct RefreshCommitCoin`. - */ -struct RefreshCommitLink -{ - /** - * Transfer public key (FIXME: explain!) - */ - struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; - - /** - * Encrypted shared secret to decrypt the link. - */ - struct TALER_EncryptedLinkSecret shared_secret_enc; -}; - - -/** - * Store the commitment to the given (encrypted) refresh link data - * for the given refresh session. - * - * @param db_conn database connection to use - * @param refresh_session_pub public key of the refresh session this - * commitment belongs with - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to melted (old) coins - * @param commit_link link information to store - * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success - */ -int -TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - const struct RefreshCommitLink *commit_link); - -/** - * Obtain the commited (encrypted) refresh link data - * for the given refresh session. - * - * @param db_conn database connection to use - * @param refresh_session_pub public key of the refresh session this - * commitment belongs with - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to melted (old) coins - * @param cc[OUT] link information to return - * @return #GNUNET_SYSERR on internal error, - * #GNUNET_NO if commitment was not found - * #GNUNET_OK on success - */ -int -TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, - unsigned int i, - unsigned int j, - struct RefreshCommitLink *cc); - - -/** - * Insert signature of a new coin generated during refresh into - * the database indexed by the refresh session and the index - * of the coin. This data is later used should an old coin - * be used to try to obtain the private keys during "/refresh/link". - * - * @param db_conn database connection - * @param session_pub refresh session - * @param newcoin_index coin index - * @param ev_sig coin signature - * @return #GNUNET_OK on success - */ -int -TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn, - const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, - uint16_t newcoin_index, - const struct GNUNET_CRYPTO_rsa_Signature *ev_sig); - - -/** - * Linked list of refresh information linked to a coin. - */ -struct LinkDataList -{ - /** - * Information is stored in a NULL-terminated linked list. - */ - struct LinkDataList *next; - - /** - * Link data, used to recover the private key of the coin - * by the owner of the old coin. - */ - struct TALER_RefreshLinkEncrypted *link_data_enc; - - /** - * Denomination public key, determines the value of the coin. - */ - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - - /** - * Signature over the blinded envelope. - */ - struct GNUNET_CRYPTO_rsa_Signature *ev_sig; -}; - - -/** - * Obtain the link data of a coin, that is the encrypted link - * information, the denomination keys and the signatures. - * - * @param db_conn database connection - * @param coin_pub public key to use to retrieve linkage data - * @return all known link data for the coin - */ -struct LinkDataList * -TALER_db_get_link (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); - - -/** - * Free memory of the link data list. - * - * @param ldl link data list to release - */ -void -TALER_db_link_data_list_free (struct LinkDataList *ldl); - - -/** - * Obtain shared secret and transfer public key from the public key of - * the coin. This information and the link information returned by - * #TALER_db_get_link() enable the owner of an old coin to determine - * the private keys of the new coins after the melt. - * - * - * @param db_conn database connection - * @param coin_pub public key of the coin - * @param transfer_pub[OUT] public transfer key - * @param shared_secret_enc[OUT] set to shared secret - * @return #GNUNET_OK on success, - * #GNUNET_NO on failure (not found) - * #GNUNET_SYSERR on internal failure (database issue) - */ -int -TALER_db_get_transfer (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, - struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, - struct TALER_EncryptedLinkSecret *shared_secret_enc); - - -/** - * Specification for a /lock operation. - */ -struct Lock -{ - /** - * Information about the coin that is being melted. - */ - struct TALER_CoinPublicInfo coin; - - /** - * Signature over the melting operation. - */ - const struct GNUNET_CRYPTO_EcdsaSignature coin_sig; - - /** - * How much value is being melted? - */ - struct TALER_Amount amount; - - // FIXME: more needed... -}; - - -/** - * Test if the given /lock request is known to us. - * - * @param db_conn database connection - * @param lock lock operation - * @return #GNUNET_YES if known, - * #GNUENT_NO if not, - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_have_lock (PGconn *db_conn, - const struct Lock *lock); - - -/** - * Store the given /lock request in the database. - * - * @param db_conn database connection - * @param lock lock operation - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -int -TALER_MINT_DB_insert_lock (PGconn *db_conn, - const struct Lock *lock); - - -/** - * Enumeration to classify the different types of transactions - * that can be done with a coin. - */ -enum TALER_MINT_DB_TransactionType -{ - /** - * /deposit operation. - */ - TALER_MINT_DB_TT_DEPOSIT = 0, - - /** - * /refresh/melt operation. - */ - TALER_MINT_DB_TT_REFRESH_MELT = 1, - - /** - * /lock operation. - */ - TALER_MINT_DB_TT_LOCK = 2 -}; - - -/** - * List of transactions we performed for a particular coin. - */ -struct TALER_MINT_DB_TransactionList -{ - - /** - * Next pointer in the NULL-terminated linked list. - */ - struct TALER_MINT_DB_TransactionList *next; - - /** - * Type of the transaction, determines what is stored in @e details. - */ - enum TALER_MINT_DB_TransactionType type; - - /** - * Details about the transaction, depending on @e type. - */ - union - { - - /** - * Details if transaction was a /deposit operation. - */ - struct Deposit *deposit; - - /** - * Details if transaction was a /refresh/melt operation. - */ - struct RefreshMelt *melt; - - /** - * Details if transaction was a /lock operation. - */ - struct Lock *lock; - - } details; - -}; - - -/** - * Compile a list of all (historic) transactions performed - * with the given coin (/refresh/melt and /deposit operations). - * - * @param db_conn database connection - * @param coin_pub coin to investigate - * @return list of transactions, NULL if coin is fresh - */ -struct TALER_MINT_DB_TransactionList * -TALER_MINT_DB_get_coin_transactions (PGconn *db_conn, - const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); - - -/** - * Free linked list of transactions. - * - * @param list list to free - */ -void -TALER_MINT_DB_free_coin_transaction_list (struct TALER_MINT_DB_TransactionList *list); - - - -#endif /* _NEURO_MINT_DB_H */ diff --git a/src/mint/plugin.c b/src/mint/plugin.c new file mode 100644 index 00000000..91cd3f40 --- /dev/null +++ b/src/mint/plugin.c @@ -0,0 +1,183 @@ +/* + This file is part of TALER + Copyright (C) 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see +*/ +/** + * @file mint/plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "plugin.h" +#include + + +/** + * Global variable with the plugin (once loaded). + */ +struct TALER_MINTDB_Plugin *plugin; + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +int +TALER_MINT_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg) +{ + return GNUNET_SYSERR; +} + + +/** + * Shutdown the plugin. + */ +void +TALER_MINT_plugin_unload () +{ + if (NULL == plugin) + return; +} + + +/** + * Setup libtool paths. + */ +void __attribute__ ((constructor)) +plugin_init () +{ + int err; + const char *opath; + char *path; + char *cpath; + + err = lt_dlinit (); + if (err > 0) + { + FPRINTF (stderr, + _("Initialization of plugin mechanism failed: %s!\n"), + lt_dlerror ()); + return; + } + opath = lt_dlgetsearchpath (); + if (NULL != opath) + old_dlsearchpath = GNUNET_strdup (opath); + path = TALER_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR); + if (NULL != path) + { + if (NULL != opath) + { + GNUNET_asprintf (&cpath, "%s:%s", opath, path); + lt_dlsetsearchpath (cpath); + GNUNET_free (path); + GNUNET_free (cpath); + } + else + { + lt_dlsetsearchpath (path); + GNUNET_free (path); + } + } +} + + +/** + * Shutdown libtool. + */ +void __attribute__ ((destructor)) +plugin_fini () +{ + lt_dlsetsearchpath (old_dlsearchpath); + if (NULL != old_dlsearchpath) + { + GNUNET_free (old_dlsearchpath); + old_dlsearchpath = NULL; + } + lt_dlexit (); +} + + +// FIXME: decide if we should keep these in each plugin, here +// or yet again somewhere else entirely (plugin_common.c?) + +/** + * Free memory associated with the given reserve history. + * + * @param rh history to free. + */ +void +TALER_MINT_DB_free_reserve_history (struct ReserveHistory *rh) +{ + struct BankTransfer *bt; + struct CollectableBlindcoin *cbc; + struct ReserveHistory *backref; + + while (NULL != rh) + { + switch(rh->type) + { + case TALER_MINT_DB_RO_BANK_TO_MINT: + bt = rh->details.bank; + if (NULL != bt->wire) + json_decref ((json_t *) bt->wire); /* FIXME: avoid cast? */ + GNUNET_free (bt); + break; + case TALER_MINT_DB_RO_WITHDRAW_COIN: + cbc = rh->details.withdraw; + GNUNET_CRYPTO_rsa_signature_free (cbc->sig); + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub); + GNUNET_free (cbc); + break; + } + backref = rh; + rh = rh->next; + GNUNET_free (backref); + } +} + + +/** + * Free memory of the link data list. + * + * @param ldl link data list to release + */ +void +TALER_db_link_data_list_free (struct LinkDataList *ldl) +{ + GNUNET_break (0); // FIXME +} + + +/** + * Free linked list of transactions. + * + * @param list list to free + */ +void +TALER_MINT_DB_free_coin_transaction_list (struct TALER_MINT_DB_TransactionList *list) +{ + // FIXME: check logic! + GNUNET_break (0); +} + + + +/* end of plugin.c */ diff --git a/src/mint/plugin.h b/src/mint/plugin.h new file mode 100644 index 00000000..01b99ebc --- /dev/null +++ b/src/mint/plugin.h @@ -0,0 +1,77 @@ +/* + This file is part of TALER + Copyright (C) 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see +*/ +/** + * @file mint/plugin.h + * @brief Logic to load database plugins + * @author Christian Grothoff + */ +#ifndef PLUGIN_H +#define PLUGIN_H + +#include +#include "taler_mintdb_plugin.h" + +/** + * Global variable with the plugin (once loaded). + */ +extern struct TALER_MINTDB_Plugin *plugin; + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +int +TALER_MINT_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown the plugin. + */ +void +TALER_MINT_plugin_unload (void); + + +/** + * Free memory associated with the given reserve history. + * + * @param rh history to free. + */ +void +TALER_MINT_DB_free_reserve_history (struct ReserveHistory *rh); + + +/** + * Free memory of the link data list. + * + * @param ldl link data list to release + */ +void +TALER_db_link_data_list_free (struct LinkDataList *ldl); + + +/** + * Free linked list of transactions. + * + * @param list list to free + */ +void +TALER_MINT_DB_free_coin_transaction_list (struct TALER_MINT_DB_TransactionList *list); + + +#endif diff --git a/src/mint/plugin_mintdb_postgres.c b/src/mint/plugin_mintdb_postgres.c new file mode 100644 index 00000000..8935fe03 --- /dev/null +++ b/src/mint/plugin_mintdb_postgres.c @@ -0,0 +1,2316 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see +*/ + +/** + * @file plugin_mintdb_postgres.c + * @brief Low-level (statement-level) Postgres database access for the mint + * @author Florian Dold + * @author Christian Grothoff + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "db_pq.h" +#include "taler_signatures.h" +#include "taler_mintdb_plugin.h" +#include +#include + + +#define TALER_TEMP_SCHEMA_NAME "taler_temporary" + +#define QUERY_ERR(result) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) + + +#define BREAK_DB_ERR(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +#define SQLEXEC_(conn, sql, result) \ + do { \ + result = PQexec (conn, sql); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + goto SQLEXEC_fail; \ + } \ + PQclear (result); result = NULL; \ + } while (0) + +/** + * This the length of the currency strings (without 0-termination) we use. Note + * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the + * DB only needs to store 3 bytes instead of 8 bytes. + */ +#define TALER_DB_CURRENCY_LEN 3 + + +/** + * Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session +{ + /** + * Postgres connection handle. + */ + PGconn *conn; +}; + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Thread-local database connection. + * Contains a pointer to PGconn or NULL. + */ + pthread_key_t db_conn_threadlocal; + + /** + * Database connection string, as read from + * the configuration. + */ + char *TALER_MINT_db_connection_cfg_str; +}; + + + +/** + * Set the given connection to use a temporary schema + * + * @param db the database connection + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error + */ +static int +set_temporary_schema (PGconn *db) +{ + PGresult *result; + + SQLEXEC_(db, + "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" + "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Drop the temporary taler schema. This is only useful for testcases + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_temporary (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + SQLEXEC_ (session->conn, + "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Create the necessary tables if they are not present + * + * @param pc our overall context + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_create_tables (struct PostgresClosure *pc, + int temporary) +{ + PGresult *result; + PGconn *conn; + + result = NULL; + conn = PQconnectdb (pc->TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != PQstatus (conn)) + { + LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (conn)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ((GNUNET_YES == temporary) + && (GNUNET_SYSERR == set_temporary_schema (conn))) + { + PQfinish (conn); + return GNUNET_SYSERR; + } +#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); + /* reserves table is for summarization of a reserve. It is updated when new + funds are added and existing funds are withdrawn */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" + "(" + " reserve_pub BYTEA PRIMARY KEY" + ",current_balance_value INT4 NOT NULL" + ",current_balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ")"); + /* reserves_in table collects the transactions which transfer funds into the + reserve. The amount and expiration date for the corresponding reserve are + updated when new transfer funds are added. The rows of this table + correspond to each incoming transaction. */ + SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" + "(" + " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",balance_value INT4 NOT NULL" + ",balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ");"); + /* Create an index on the foreign key as it is not created automatically by PSQL */ + SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" + " ON reserves_in (reserve_pub);"); + SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" + "(" + "blind_ev BYTEA PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",reserve_sig BYTEA NOT NULL" + ");"); + SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" + " collectable_blindcoins (reserve_pub)"); + SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " + "(" + " coin_pub BYTEA NOT NULL PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" + ",denom_sig BYTEA NOT NULL" + ",expended_value INT4 NOT NULL" + ",expended_fraction INT4 NOT NULL" + ",expended_currency VARCHAR(4) NOT NULL" + ",refresh_session_pub BYTEA" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " + "(" + " session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)" + ",session_melt_sig BYTEA" + ",session_commit_sig BYTEA" + ",noreveal_index INT2 NOT NULL" + // non-zero if all reveals were ok + // and the new coin signatures are ready + ",reveal_ok BOOLEAN NOT NULL DEFAULT false" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " + "( " + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",newcoin_index INT2 NOT NULL " + ",denom_pub BYTEA NOT NULL " + ",PRIMARY KEY (session_pub, newcoin_index)" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)" + ",transfer_pub BYTEA NOT NULL" + ",link_secret_enc BYTEA NOT NULL" + // index of the old coin in the customer's request + ",oldcoin_index INT2 NOT NULL" + // index for cut and choose, + // ranges from 0 to kappa-1 + ",cnc_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",link_vector_enc BYTEA NOT NULL" + // index of the new coin in the customer's request + ",newcoin_index INT2 NOT NULL" + // index for cut and choose, + ",cnc_index INT2 NOT NULL" + ",coin_ev BYTEA NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " + ",denom_pub BYTEA NOT NULL " + ",oldcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" + "(" + " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) " + ",ev_sig BYTEA NOT NULL" + ",newcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " + "( " + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",transaction_id INT8 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",amount_value INT4 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=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" + ")"); +#undef SQLEXEC + PQfinish (conn); + return GNUNET_OK; + + SQLEXEC_fail: + PQfinish (conn); + return GNUNET_SYSERR; +} + + +/** + * Setup prepared statements. + * + * @param db_conn connection handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +postgres_prepare (PGconn *db_conn) +{ + PGresult *result; + +#define PREPARE(name, sql, ...) \ + do { \ + result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + return GNUNET_SYSERR; \ + } \ + PQclear (result); result = NULL; \ + } while (0); + + PREPARE ("get_reserve", + "SELECT " + "current_balance_value" + ",current_balance_fraction" + ",balance_currency " + ",expiration_date " + "FROM reserves " + "WHERE reserve_pub=$1 " + "LIMIT 1; ", + 1, NULL); + PREPARE ("create_reserve", + "INSERT INTO reserves (" + " reserve_pub," + " current_balance_value," + " current_balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + "$1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("update_reserve", + "UPDATE reserves " + "SET" + " current_balance_value=$2 " + ",current_balance_fraction=$3 " + ",expiration_date=$4 " + "WHERE reserve_pub=$1 ", + 4, NULL); + PREPARE ("create_reserves_in_transaction", + "INSERT INTO reserves_in (" + " reserve_pub," + " balance_value," + " balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + " $1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("get_reserves_in_transactions", + "SELECT" + " balance_value" + ",balance_fraction" + ",balance_currency" + ",expiration_date" + " FROM reserves_in WHERE reserve_pub=$1", + 1, NULL); + PREPARE ("insert_collectable_blindcoin", + "INSERT INTO collectable_blindcoins ( " + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_pub, reserve_sig) " + "VALUES ($1, $2, $3, $4, $5)", + 5, NULL); + PREPARE ("get_collectable_blindcoin", + "SELECT " + " denom_pub, denom_sig" + ",reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_ev = $1", + 1, NULL); + PREPARE ("get_reserves_blindcoins", + "select" + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_sig" + " FROM collectable_blindcoins" + " WHERE reserve_pub=$1;", + 1, NULL); + + /* FIXME: does it make sense to store these computed values in the DB? */ +#if 0 + PREPARE ("get_refresh_session", + "SELECT " + " (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa " + ",noreveal_index" + ",session_commit_sig " + ",reveal_ok " + "FROM refresh_sessions " + "WHERE session_pub = $1", + 1, NULL); +#endif + + PREPARE ("get_known_coin", + "SELECT " + " coin_pub, denom_pub, denom_sig " + ",expended_value, expended_fraction, expended_currency " + ",refresh_session_pub " + "FROM known_coins " + "WHERE coin_pub = $1", + 1, NULL); + PREPARE ("update_known_coin", + "UPDATE known_coins " + "SET " + " denom_pub = $2 " + ",denom_sig = $3 " + ",expended_value = $4 " + ",expended_fraction = $5 " + ",expended_currency = $6 " + ",refresh_session_pub = $7 " + "WHERE " + " coin_pub = $1 ", + 7, NULL); + PREPARE ("insert_known_coin", + "INSERT INTO known_coins (" + " coin_pub" + ",denom_pub" + ",denom_sig" + ",expended_value" + ",expended_fraction" + ",expended_currency" + ",refresh_session_pub" + ")" + "VALUES ($1,$2,$3,$4,$5,$6,$7)", + 7, NULL); + PREPARE ("get_refresh_commit_link", + "SELECT " + " transfer_pub " + ",link_secret_enc " + "FROM refresh_commit_link " + "WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3", + 3, NULL); + PREPARE ("get_refresh_commit_coin", + "SELECT " + " link_vector_enc " + ",coin_ev " + "FROM refresh_commit_coin " + "WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3", + 3, NULL); + PREPARE ("insert_refresh_order", + "INSERT INTO refresh_order ( " + " newcoin_index " + ",session_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("insert_refresh_melt", + "INSERT INTO refresh_melt ( " + " session_pub " + ",oldcoin_index " + ",coin_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3, $4) ", + 3, NULL); + PREPARE ("get_refresh_order", + "SELECT denom_pub " + "FROM refresh_order " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_collectable", + "SELECT ev_sig " + "FROM refresh_collectable " + "WHERE session_pub = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_melt", + "SELECT coin_pub " + "FROM refresh_melt " + "WHERE session_pub = $1 AND oldcoin_index = $2", + 2, NULL); + PREPARE ("insert_refresh_session", + "INSERT INTO refresh_sessions ( " + " session_pub " + ",noreveal_index " + ") " + "VALUES ($1, $2) ", + 2, NULL); + PREPARE ("insert_refresh_commit_link", + "INSERT INTO refresh_commit_link ( " + " session_pub " + ",transfer_pub " + ",cnc_index " + ",oldcoin_index " + ",link_secret_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_commit_coin", + "INSERT INTO refresh_commit_coin ( " + " session_pub " + ",coin_ev " + ",cnc_index " + ",newcoin_index " + ",link_vector_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_collectable", + "INSERT INTO refresh_collectable ( " + " session_pub " + ",newcoin_index " + ",ev_sig " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("set_reveal_ok", + "UPDATE refresh_sessions " + "SET reveal_ok = TRUE " + "WHERE session_pub = $1 ", + 1, NULL); + PREPARE ("get_link", + "SELECT link_vector_enc, ro.denom_pub, ev_sig " + "FROM refresh_melt rm " + " JOIN refresh_order ro USING (session_pub) " + " JOIN refresh_commit_coin rcc USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + " JOIN refresh_collectable rc USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND ro.newcoin_index = rcc.newcoin_index " + "AND ro.newcoin_index = rc.newcoin_index " + "AND rcc.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub " + " ) ", + 1, NULL); + PREPARE ("get_transfer", + "SELECT transfer_pub, link_secret_enc " + "FROM refresh_melt rm " + " JOIN refresh_commit_link rcl USING (session_pub) " + " JOIN refresh_sessions rs USING (session_pub) " + "WHERE rm.coin_pub = $1 " + "AND rm.oldcoin_index = rcl.oldcoin_index " + "AND rcl.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub " + " ) ", + 1, NULL); + PREPARE ("insert_deposit", + "INSERT INTO deposits (" + "coin_pub," + "denom_pub," + "denom_sig," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig," + "wire" + ") VALUES (" + "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" + ")", + 12, NULL); + PREPARE ("get_deposit", + "SELECT " + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig" + " FROM deposits WHERE (" + "(coin_pub = $1) AND" + "(transaction_id = $2) AND" + "(merchant_pub = $3)" + ")", + 3, NULL); + return GNUNET_OK; +#undef PREPARE +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ + PGconn *db_conn = cls; + + if (NULL != db_conn) + PQfinish (db_conn); +} + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + * database default one + * @return the database connection, or NULL on error + */ +static struct TALER_MINTDB_Session * +postgres_get_connection (void *cls, + int temporary) +{ + struct PostgresClosure *pc = cls; + PGconn *db_conn; + struct TALER_MINTDB_Session *session; + + if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) + return session; + db_conn = PQconnectdb (pc->TALER_MINT_db_connection_cfg_str); + if (CONNECTION_OK != + PQstatus (db_conn)) + { + LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (db_conn)); + GNUNET_break (0); + return NULL; + } + if ((GNUNET_YES == temporary) + && (GNUNET_SYSERR == set_temporary_schema(db_conn))) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + postgres_prepare (db_conn)) + { + GNUNET_break (0); + return NULL; + } + session = GNUNET_new (struct TALER_MINTDB_Session); + session->conn = db_conn; + if (0 != pthread_setspecific (pc->db_conn_threadlocal, + session)) + { + GNUNET_break (0); + // FIXME: close db_conn! + GNUNET_free (session); + return NULL; + } + return session; +} + + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_start (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "BEGIN"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + LOG_ERROR ("Failed to start transaction: %s\n", + PQresultErrorMessage (result)); + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static void +postgres_rollback (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "ROLLBACK"); + GNUNET_break (PGRES_COMMAND_OK == + PQresultStatus (result)); + PQclear (result); +} + + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_commit (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "COMMIT"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get the summary of a reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve data. The public key of the reserve should be set + * in this structure; it is used to query the database. The balance + * and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_reserve_get (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + uint64_t expiration_date_nbo; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(reserve->pub), + TALER_DB_QUERY_PARAM_END + }; + + if (NULL == reserve->pub) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + result = TALER_DB_exec_prepared (session->conn, + "get_reserve", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("expiration_date", &expiration_date_nbo), + TALER_DB_RESULT_SPEC_END + }; + EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)); + EXITIF (GNUNET_OK != + TALER_DB_extract_amount (result, 0, + "current_balance_value", + "current_balance_fraction", + "balance_currency", + &reserve->balance)); + reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); + PQclear (result); + return GNUNET_OK; + + EXITIF_exit: + PQclear (result); + return GNUNET_SYSERR; +} + + +/** + * Updates a reserve with the data from the given reserve structure. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @param reserve the reserve structure whose data will be used to update the + * corresponding record in the database. + * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error + */ +static int +postgres_reserves_update (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + int ret; + + if ((NULL == reserve) || (NULL == reserve->pub)) + return GNUNET_SYSERR; + ret = GNUNET_OK; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve->pub), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), + TALER_DB_QUERY_PARAM_END + }; + TALER_amount_hton (&balance_nbo, + &reserve->balance); + expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); + result = TALER_DB_exec_prepared (session->conn, + "update_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + ret = GNUNET_SYSERR; + } + PQclear (result); + return ret; +} + + +/** + * Insert a incoming transaction into reserves. New reserves are also created + * through this function. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve structure. The public key of the reserve should + * be set here. Upon successful execution of this function, the + * balance and expiration of the reserve will be updated. + * @param balance the amount that has to be added to the reserve + * @param expiry the new expiration time for the reserve + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures + */ +static int +postgres_reserves_in_insert (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve, + const struct TALER_Amount *balance, + const struct GNUNET_TIME_Absolute expiry) +{ + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + PGresult *result; + int reserve_exists; + + result = NULL; + if (NULL == reserve) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != postgres_start (cls, + session)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + reserve_exists = postgres_reserve_get (cls, + session, + reserve); + if (GNUNET_SYSERR == reserve_exists) + { + postgres_rollback (cls, + session); + return GNUNET_SYSERR; + } + TALER_amount_hton (&balance_nbo, + balance); + expiry_nbo = GNUNET_TIME_absolute_hton (expiry); + if (GNUNET_NO == reserve_exists) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Reserve does not exist; creating a new one\n"); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve->pub), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, + TALER_DB_CURRENCY_LEN), + TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "create_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + } + if (NULL != result) + PQclear (result); + result = NULL; + /* create new incoming transaction */ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve->pub), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_DB_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, + TALER_DB_CURRENCY_LEN), + TALER_DB_QUERY_PARAM_PTR (&expiry_nbo), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "create_reserves_in_transaction", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + PQclear (result); + result = NULL; + if (GNUNET_NO == reserve_exists) + { + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = *balance; + reserve->expiry = expiry; + return GNUNET_OK; + } + /* Update reserve */ + struct Reserve updated_reserve; + updated_reserve.pub = reserve->pub; + + if (GNUNET_OK != + TALER_amount_add (&updated_reserve.balance, + &reserve->balance, + balance)) + { + return GNUNET_SYSERR; + } + updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &updated_reserve)) + goto rollback; + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = updated_reserve.balance; + reserve->expiry = updated_reserve.expiry; + return GNUNET_OK; + + rollback: + PQclear (result); + postgres_rollback (cls, + session); + return GNUNET_SYSERR; +} + + +/** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_get_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (h_blind), + TALER_DB_QUERY_PARAM_END + }; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *denom_sig; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub = NULL; + denom_pub_enc = NULL; + denom_sig_enc = NULL; + result = TALER_DB_exec_prepared (session->conn, + "get_collectable_blindcoin", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_DB_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), + TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + GNUNET_break (0); + goto cleanup; + } + denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + if ((NULL == denom_pub) || (NULL == denom_sig)) + { + GNUNET_break (0); + goto cleanup; + } + collectable->denom_pub = denom_pub; + collectable->sig = denom_sig; + ret = GNUNET_YES; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + if (GNUNET_YES != ret) + { if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != denom_sig) + GNUNET_CRYPTO_rsa_signature_free (denom_sig); + } + return ret; +} + + +/** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_insert_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, + const struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct Reserve reserve; + char *denom_pub_enc = NULL; + char *denom_sig_enc = NULL; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (collectable->sig, + &denom_sig_enc); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (h_blind), + TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), + TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub), + TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig), + TALER_DB_QUERY_PARAM_END + }; + if (GNUNET_OK != postgres_start (cls, + session)) + goto cleanup; + result = TALER_DB_exec_prepared (session->conn, + "insert_collectable_blindcoin", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto rollback; + } + reserve.pub = (struct GNUNET_CRYPTO_EddsaPublicKey *) + &collectable->reserve_pub; + if (GNUNET_OK != postgres_reserve_get (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_SYSERR == + TALER_amount_subtract (&reserve.balance, + &reserve.balance, + &withdraw)) + goto rollback; + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_OK == postgres_commit (cls, + session)) + { + ret = GNUNET_OK; + goto cleanup; + } + + rollback: + postgres_rollback (cls, + session); + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + return ret; +} + + +/** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ +static struct ReserveHistory * +postgres_get_reserve_history (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub) +{ + PGresult *result; + struct ReserveHistory *rh; + struct ReserveHistory *rh_head; + int rows; + int ret; + + result = NULL; + rh = NULL; + rh_head = NULL; + ret = GNUNET_SYSERR; + { + struct BankTransfer *bt; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve_pub), + TALER_DB_QUERY_PARAM_END + }; + + result = TALER_DB_exec_prepared (session->conn, + "get_reserves_in_transactions", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asked to fetch history for an unknown reserve.\n"); + goto cleanup; + } + while (0 < rows) + { + bt = GNUNET_new (struct BankTransfer); + if (GNUNET_OK != TALER_DB_extract_amount (result, + --rows, + "balance_value", + "balance_fraction", + "balance_currency", + &bt->amount)) + { + GNUNET_free (bt); + GNUNET_break (0); + goto cleanup; + } + (void) memcpy (&bt->reserve_pub, reserve_pub, sizeof (bt->reserve_pub)); + if (NULL != rh_head) + { + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + } + else + { + rh_head = GNUNET_new (struct ReserveHistory); + rh = rh_head; + } + rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; + rh_head->details.bank = bt; + } + } + PQclear (result); + result = NULL; + { + struct GNUNET_HashCode blind_ev; + struct GNUNET_CRYPTO_EddsaSignature reserve_sig; + struct CollectableBlindcoin *cbc; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (reserve_pub), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "get_reserves_blindcoins", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */ + goto cleanup; + } + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC ("blind_ev", &blind_ev), + TALER_DB_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_DB_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_DB_RESULT_SPEC ("reserve_sig", &reserve_sig), + TALER_DB_RESULT_SPEC_END + }; + GNUNET_assert (NULL != rh); + GNUNET_assert (NULL != rh_head); + GNUNET_assert (NULL == rh_head->next); + while (0 < rows) + { + if (GNUNET_YES != TALER_DB_extract_result (result, rs, --rows)) + { + GNUNET_break (0); + goto cleanup; + } + cbc = GNUNET_new (struct CollectableBlindcoin); + cbc->sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + GNUNET_free (denom_sig_enc); + denom_sig_enc = NULL; + cbc->denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + GNUNET_free (denom_pub_enc); + denom_pub_enc = NULL; + if ((NULL == cbc->sig) || (NULL == cbc->denom_pub)) + { + if (NULL != cbc->sig) + GNUNET_CRYPTO_rsa_signature_free (cbc->sig); + if (NULL != cbc->denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub); + GNUNET_free (cbc); + GNUNET_break (0); + goto cleanup; + } + (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); + (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); + (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; + rh_head->details.withdraw = cbc; + } + } + ret = GNUNET_OK; + + cleanup: + if (NULL != result) + PQclear (result); + if (GNUNET_SYSERR == ret) + { + TALER_MINT_DB_free_reserve_history (rh); + rh = NULL; + } + return rh; +} + + +/** + * Check if we have the specified deposit already in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + * #GNUNET_NO if this deposit is unknown to us + */ +static int +postgres_have_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result; + int ret; + + ret = GNUNET_SYSERR; + result = TALER_DB_exec_prepared (session->conn, + "get_deposit", + params); + if (PGRES_TUPLES_OK != + PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + ret = GNUNET_YES; + + cleanup: + PQclear (result); + return ret; +} + + +/** + * Insert information about deposited coin into the + * database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +postgres_insert_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + char *denom_pub_enc; + char *denom_sig_enc; + char *json_wire_enc; + PGresult *result; + struct TALER_AmountNBO amount_nbo; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig, + &denom_sig_enc); + json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); + TALER_amount_hton (&amount_nbo, + &deposit->amount); + struct TALER_DB_QueryParam params[]= { + TALER_DB_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_DB_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), + TALER_DB_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), + TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_DB_QUERY_PARAM_PTR (&amount_nbo.value), + TALER_DB_QUERY_PARAM_PTR (&amount_nbo.fraction), + TALER_DB_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, + TALER_CURRENCY_LEN - 1), + TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract), + TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire), + TALER_DB_QUERY_PARAM_PTR (&deposit->csig), + TALER_DB_QUERY_PARAM_PTR_SIZED (json_wire_enc, + strlen (json_wire_enc)), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, "insert_deposit", params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + ret = GNUNET_OK; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + GNUNET_free_non_null (json_wire_enc); + return ret; +} + + +/** + * Lookup refresh session data under the given public key. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param refresh_session_pub public key to use for the lookup + * @param refresh_session[OUT] where to store the result + * @return #GNUNET_YES on success, + * #GNUNET_NO if not found, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_get_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *refresh_session) +{ + // FIXME: check logic! + int res; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, + "get_refresh_session", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Query failed: %s\n", + PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* We're done if the caller is only interested in + * whether the session exists or not */ + + if (NULL == refresh_session) + return GNUNET_YES; + + memset (session, 0, sizeof (struct RefreshSession)); + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins), + TALER_DB_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins), + TALER_DB_RESULT_SPEC("kappa", &refresh_session->kappa), + TALER_DB_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index), + TALER_DB_RESULT_SPEC_END + }; + + res = TALER_DB_extract_result (result, rs, 0); + + if (GNUNET_OK != res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins); + refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins); + refresh_session->kappa = ntohs (refresh_session->kappa); + refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index); + + PQclear (result); + return GNUNET_YES; +} + + +/** + * Store new refresh session data under the given public key. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param refresh_session_pub public key to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_create_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct RefreshSession *refresh_session) +{ + // FIXME: actually store session data! + uint16_t noreveal_index; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&noreveal_index), + TALER_DB_QUERY_PARAM_END + }; + + noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); + noreveal_index = htonl (noreveal_index); + + PGresult *result = TALER_DB_exec_prepared (session->conn, + "insert_refresh_session", + params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Store the given /refresh/melt request in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to store + * @param melt melt operation + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, + uint16_t oldcoin_index, + const struct RefreshMelt *melt) +{ + // FIXME: check logic! + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub, + &buf); + { + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&melt->coin.coin_pub), + TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "insert_refresh_melt", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get information about melted coin details from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, + uint16_t oldcoin_index, + struct RefreshMelt *melt) +{ + // FIXME: check logic! + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_pub refresh session key + * @param newcoin_index index of the coin to generate + * @param denom_pub denomination of the coin to create + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub) +{ + // FIXME: check logic + uint16_t newcoin_index_nbo = htons (newcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pub, + &buf); + + { + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR (&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR (session_pub), + TALER_DB_QUERY_PARAM_PTR_SIZED (buf, buf_size), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "insert_refresh_order", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Lookup in the database the @a newcoin_index coin that we want to + * create in the given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_pub refresh session key + * @param newcoin_index index of the coin to generate + * @param denom_pub denomination of the coin to create + * @return NULL on error (not found or internal error) + */ +static struct GNUNET_CRYPTO_rsa_PublicKey * +postgres_get_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index) +{ + // FIXME: check logic + char *buf; + size_t buf_size; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + uint16_t newcoin_index_nbo = htons (newcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, "get_refresh_order", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return NULL; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + /* FIXME: may want to distinguish between different error cases! */ + return NULL; + } + GNUNET_assert (1 == PQntuples (result)); + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), + TALER_DB_RESULT_SPEC_END + }; + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return NULL; + } + PQclear (result); + denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size); + GNUNET_free (buf); + return denom_pub; +} + + + +/** + * Store information about the commitment of the + * given coin for the given refresh session in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param refresh_session_pub refresh session this commitment belongs to + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin coin commitment to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error + */ +static int +postgres_insert_refresh_commit_coin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + const struct RefreshCommitCoin *commit_coin) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (i); + uint16_t newcoin_index_nbo = htons (j); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coin->coin_ev, commit_coin->coin_ev_size), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(commit_coin->refresh_link->coin_priv_enc, + commit_coin->refresh_link->blinding_key_enc_size + + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, "insert_refresh_commit_coin", params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param refresh_session_pub refresh session the commitment belongs to + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin[OUT] coin commitment to return + * @return #GNUNET_OK on success + * #GNUNET_NO if not found + * #GNUNET_SYSERR on error + */ +static int +postgres_get_refresh_commit_coin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int cnc_index, + unsigned int newcoin_index, + struct RefreshCommitCoin *cc) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + char *c_buf; + size_t c_buf_size; + char *rl_buf; + size_t rl_buf_size; + struct TALER_RefreshLinkEncrypted *rl; + + PGresult *result = TALER_DB_exec_prepared (session->conn, "get_refresh_commit_coin", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), + TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), + TALER_DB_RESULT_SPEC_END + }; + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + if (rl_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) + { + GNUNET_free (c_buf); + GNUNET_free (rl_buf); + return GNUNET_SYSERR; + } + rl = TALER_refresh_link_encrypted_decode (rl_buf, + rl_buf_size); + GNUNET_free (rl_buf); + cc->refresh_link = rl; + cc->coin_ev = c_buf; + cc->coin_ev_size = c_buf_size; + return GNUNET_YES; +} + + +/** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param refresh_session_pub public key of the refresh session this + * commitment belongs with + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param commit_link link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ +static int +postgres_insert_refresh_commit_link (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + const struct RefreshCommitLink *commit_link) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (i); + uint16_t oldcoin_index_nbo = htons (j); + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, + "insert_refresh_commit_link", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param refresh_session_pub public key of the refresh session this + * commitment belongs with + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param cc[OUT] link information to return + * @return #GNUNET_SYSERR on internal error, + * #GNUNET_NO if commitment was not found + * #GNUNET_OK on success + */ +static int +postgres_get_refresh_commit_link (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int cnc_index, + unsigned int oldcoin_index, + struct RefreshCommitLink *cc) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(refresh_session_pub), + TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, + "get_refresh_commit_link", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", &cc->transfer_pub), + TALER_DB_RESULT_SPEC("link_secret_enc", &cc->shared_secret_enc), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_free (cc); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_pub refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ +static int +postgres_insert_refresh_collectable (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_rsa_Signature *ev_sig) +{ + // FIXME: check logic! + uint16_t newcoin_index_nbo = htons (newcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig, + &buf); + { + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(session_pub), + TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_DB_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_DB_QUERY_PARAM_END + }; + result = TALER_DB_exec_prepared (session->conn, + "insert_refresh_collectable", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key to use to retrieve linkage data + * @return all known link data for the coin + */ +static struct LinkDataList * +postgres_get_link (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) +{ + // FIXME: check logic! + struct LinkDataList *ldl; + struct LinkDataList *pos; + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + PGresult *result = TALER_DB_exec_prepared (session->conn, "get_link", params); + + ldl = NULL; + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return NULL; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return NULL; + } + + + int i = 0; + + for (i = 0; i < PQntuples (result); i++) + { + struct TALER_RefreshLinkEncrypted *link_enc; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *sig; + char *ld_buf; + size_t ld_buf_size; + char *pk_buf; + size_t pk_buf_size; + char *sig_buf; + size_t sig_buf_size; + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), + TALER_DB_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), + TALER_DB_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, i)) + { + PQclear (result); + GNUNET_break (0); + TALER_db_link_data_list_free (ldl); + return NULL; + } + if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) + { + PQclear (result); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + TALER_db_link_data_list_free (ldl); + return NULL; + } + // FIXME: use util API for this! + link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + + ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); + link_enc->blinding_key_enc = (const char *) &link_enc[1]; + link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); + memcpy (link_enc->coin_priv_enc, + ld_buf, + ld_buf_size); + + sig = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, + sig_buf_size); + denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, + pk_buf_size); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + if ( (NULL == sig) || + (NULL == denom_pub) ) + { + if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != sig) + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_free (link_enc); + GNUNET_break (0); + PQclear (result); + TALER_db_link_data_list_free (ldl); + return NULL; + } + pos = GNUNET_new (struct LinkDataList); + pos->next = ldl; + pos->link_data_enc = link_enc; + pos->denom_pub = denom_pub; + pos->ev_sig = sig; + ldl = pos; + } + return ldl; +} + + +/** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * #TALER_db_get_link() enable the owner of an old coin to determine + * the private keys of the new coins after the melt. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key of the coin + * @param transfer_pub[OUT] public transfer key + * @param shared_secret_enc[OUT] set to shared secret + * @return #GNUNET_OK on success, + * #GNUNET_NO on failure (not found) + * #GNUNET_SYSERR on internal failure (database issue) + */ +static int +postgres_get_transfer (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct TALER_EncryptedLinkSecret *shared_secret_enc) +{ + // FIXME: check logic! + struct TALER_DB_QueryParam params[] = { + TALER_DB_QUERY_PARAM_PTR(coin_pub), + TALER_DB_QUERY_PARAM_END + }; + + PGresult *result = TALER_DB_exec_prepared (session->conn, "get_transfer", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "got %d tuples for get_transfer\n", + PQntuples (result)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + struct TALER_DB_ResultSpec rs[] = { + TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub), + TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc), + TALER_DB_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ +static struct TALER_MINT_DB_TransactionList * +postgres_get_coin_transactions (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub) +{ + // FIXME: check logic! + GNUNET_break (0); // FIXME: implement! + return NULL; +} + + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin` + */ +void * +libtaler_plugin_mintdb_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_MINTDB_Plugin *plugin; + + pg = GNUNET_new (struct PostgresClosure); + + if (0 != pthread_key_create (&pg->db_conn_threadlocal, + &db_conn_destroy)) + { + LOG_ERROR ("Cannnot create pthread key.\n"); + return NULL; + } + /* FIXME: use configuration section with "postgres" in its name... */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "db", + &pg->TALER_MINT_db_connection_cfg_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "db"); + return NULL; + } + plugin = GNUNET_new (struct TALER_MINTDB_Plugin); + plugin->cls = pg; + + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_MINTDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_mintdb_postgres_done (void *cls) +{ + struct TALER_MINTDB_Plugin *plugin = cls; + struct PostgresClosure *pg = plugin->cls; + + GNUNET_free (pg); + GNUNET_free (plugin); + return NULL; +} + +/* end of plugin_mintdb_postgres.c */ diff --git a/src/mint/taler-mint-dbinit.c b/src/mint/taler-mint-dbinit.c index 838f98e2..060eabc7 100644 --- a/src/mint/taler-mint-dbinit.c +++ b/src/mint/taler-mint-dbinit.c @@ -22,7 +22,7 @@ #include #include #include "taler_util.h" -#include "mint_db.h" +#include "taler_mintdb_plugin.h" /** diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c index 7903d5c1..9adf26df 100644 --- a/src/mint/taler-mint-httpd.c +++ b/src/mint/taler-mint-httpd.c @@ -35,7 +35,7 @@ #include "taler-mint-httpd_withdraw.h" #include "taler-mint-httpd_refresh.h" #include "taler-mint-httpd_keystate.h" -#include "mint_db.h" +#include "taler_mintdb_plugin.h" /** @@ -260,16 +260,7 @@ mint_serve_process_config (const char *mint_directory) GNUNET_free (master_pub_str); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", "db", - &db_cfg)) - { - fprintf (stderr, - "invalid configuration: mint.db\n"); - return GNUNET_NO; - } - if (GNUNET_OK != - TALER_MINT_DB_init (db_cfg)) + TALER_MINT_plugin_load (cfg)) { fprintf (stderr, "failed to initialize DB subsystem\n"); diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 0c3675d3..292ef68f 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -29,9 +29,9 @@ #include "taler-mint-httpd_db.h" #include "taler_signatures.h" #include "taler-mint-httpd_responses.h" -#include "mint_db.h" #include "taler_util.h" #include "taler-mint-httpd_keystate.h" +#include "plugin.h" /** @@ -48,7 +48,7 @@ int TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, const struct Deposit *deposit) { - PGconn *db_conn; + struct TALER_MINTDB_Session *session; struct TALER_MINT_DB_TransactionList *tl; struct TALER_MINT_DB_TransactionList *pos; struct TALER_Amount spent; @@ -59,14 +59,16 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, struct TALER_MINT_DenomKeyIssuePriv *dki; int ret; - if (NULL == (db_conn = TALER_MINT_DB_get_connection (GNUNET_NO))) + if (NULL == (session = plugin->get_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } if (GNUNET_YES == - TALER_MINT_DB_have_deposit (db_conn, - deposit)) + plugin->have_deposit (plugin->cls, + session, + deposit)) { return TALER_MINT_reply_deposit_success (connection, &deposit->coin.coin_pub, @@ -88,13 +90,15 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, TALER_MINT_key_state_release (mks); if (GNUNET_OK != - TALER_MINT_DB_transaction (db_conn)) + plugin->start (plugin->cls, + session)) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - tl = TALER_MINT_DB_get_coin_transactions (db_conn, - &deposit->coin.coin_pub); + tl = plugin->get_coin_transactions (plugin->cls, + session, + &deposit->coin.coin_pub); spent = fee_deposit; /* fee for THIS transaction */ if (GNUNET_OK != TALER_amount_add (&spent, @@ -155,7 +159,8 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, if (0 < TALER_amount_cmp (&spent, &value)) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); ret = TALER_MINT_reply_deposit_insufficient_funds (connection, tl); TALER_MINT_DB_free_coin_transaction_list (tl); @@ -164,16 +169,19 @@ TALER_MINT_db_execute_deposit (struct MHD_Connection *connection, TALER_MINT_DB_free_coin_transaction_list (tl); if (GNUNET_OK != - TALER_MINT_DB_insert_deposit (db_conn, - deposit)) + plugin->insert_deposit (plugin->cls, + session, + deposit)) { LOG_WARNING ("Failed to store /deposit information in database\n"); - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } if (GNUNET_OK != - TALER_MINT_DB_commit (db_conn)) + plugin->commit (plugin->cls, + session)) { LOG_WARNING ("/deposit transaction commit failed\n"); return TALER_MINT_reply_commit_error (connection); @@ -200,17 +208,19 @@ int TALER_MINT_db_execute_withdraw_status (struct MHD_Connection *connection, const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub) { - PGconn *db_conn; + struct TALER_MINTDB_Session *session; struct ReserveHistory *rh; int res; - if (NULL == (db_conn = TALER_MINT_DB_get_connection (GNUNET_NO))) + if (NULL == (session = plugin->get_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - rh = TALER_MINT_DB_get_reserve_history (db_conn, - reserve_pub); + rh = plugin->get_reserve_history (plugin->cls, + session, + reserve_pub); if (NULL == rh) return TALER_MINT_reply_json_pack (connection, MHD_HTTP_NOT_FOUND, @@ -245,7 +255,7 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, size_t blinded_msg_len, const struct GNUNET_CRYPTO_EddsaSignature *signature) { - PGconn *db_conn; + struct TALER_MINTDB_Session *session; struct ReserveHistory *rh; const struct ReserveHistory *pos; struct MintKeyState *key_state; @@ -266,14 +276,16 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, blinded_msg_len, &h_blind); - if (NULL == (db_conn = TALER_MINT_DB_get_connection (GNUNET_NO))) + if (NULL == (session = plugin->get_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - res = TALER_MINT_DB_get_collectable_blindcoin (db_conn, - &h_blind, - &collectable); + res = plugin->get_collectable_blindcoin (plugin->cls, + session, + &h_blind, + &collectable); if (GNUNET_SYSERR == res) { GNUNET_break (0); @@ -305,18 +317,21 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, "Denomination not found"); } if (GNUNET_OK != - TALER_MINT_DB_transaction (db_conn)) + plugin->start (plugin->cls, + session)) { GNUNET_break (0); TALER_MINT_key_state_release (key_state); return TALER_MINT_reply_internal_db_error (connection); } - rh = TALER_MINT_DB_get_reserve_history (db_conn, - reserve); + rh = plugin->get_reserve_history (plugin->cls, + session, + reserve); if (NULL == rh) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); TALER_MINT_key_state_release (key_state); return TALER_MINT_reply_json_pack (connection, MHD_HTTP_NOT_FOUND, @@ -336,7 +351,8 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, &value, &fee_withdraw)) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); TALER_MINT_key_state_release (key_state); return TALER_MINT_reply_internal_db_error (connection); } @@ -356,7 +372,8 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, &deposit_total, &pos->details.bank->amount)) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); TALER_MINT_key_state_release (key_state); return TALER_MINT_reply_internal_db_error (connection); } @@ -375,7 +392,8 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, &withdraw_total, &value)) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); TALER_MINT_key_state_release (key_state); return TALER_MINT_reply_internal_db_error (connection); } @@ -392,7 +410,8 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, &balance)) { TALER_MINT_key_state_release (key_state); - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); res = TALER_MINT_reply_withdraw_sign_insufficient_funds (connection, rh); TALER_MINT_DB_free_reserve_history (rh); @@ -408,7 +427,8 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, if (NULL == sig) { GNUNET_break (0); - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_error (connection, "Internal error"); } @@ -420,18 +440,21 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, &collectable.h_coin_envelope); collectable.reserve_sig = *signature; if (GNUNET_OK != - TALER_MINT_DB_insert_collectable_blindcoin (db_conn, - &h_blind, - amount_required, - &collectable)) + plugin->insert_collectable_blindcoin (plugin->cls, + session, + &h_blind, + amount_required, + &collectable)) { GNUNET_break (0); GNUNET_CRYPTO_rsa_signature_free (sig); - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } if (GNUNET_OK != - TALER_MINT_DB_commit (db_conn)) + plugin->commit (plugin->cls, + session)) { LOG_WARNING ("/withdraw/sign transaction commit failed\n"); return TALER_MINT_reply_commit_error (connection); @@ -448,7 +471,7 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, * the database. * * @param connection the connection to send errors to - * @param db_conn the database connection + * @param session the database connection * @param key_state the mint's key state * @param session_pub the refresh session's public key * @param coin_public_info the coin to melt @@ -460,7 +483,7 @@ TALER_MINT_db_execute_withdraw_sign (struct MHD_Connection *connection, */ static int refresh_accept_melts (struct MHD_Connection *connection, - PGconn *db_conn, + struct TALER_MINTDB_Session *session, const struct MintKeyState *key_state, const struct GNUNET_HashCode *melt_hash, const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, @@ -489,8 +512,9 @@ refresh_accept_melts (struct MHD_Connection *connection, TALER_amount_ntoh (&coin_value, &dki->value); - tl = TALER_MINT_DB_get_coin_transactions (db_conn, - &coin_public_info->coin_pub); + tl = plugin->get_coin_transactions (plugin->cls, + session, + &coin_public_info->coin_pub); /* FIXME: #3636: compute how much value is left with this coin and compare to `expected_value`! (subtract from "coin_value") */ coin_residual = coin_value; @@ -518,10 +542,11 @@ refresh_accept_melts (struct MHD_Connection *connection, melt.melt_hash = *melt_hash; melt.amount = coin_details->melt_amount; if (GNUNET_OK != - TALER_MINT_DB_insert_refresh_melt (db_conn, - session_pub, - oldcoin_index, - &melt)) + plugin->insert_refresh_melt (plugin->cls, + session, + session_pub, + oldcoin_index, + &melt)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -570,37 +595,42 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, struct RefreshCommitLink *const* commit_link) { struct MintKeyState *key_state; - struct RefreshSession session; - PGconn *db_conn; + struct RefreshSession refresh_session; + struct TALER_MINTDB_Session *session; int res; unsigned int i; unsigned int j; - if (NULL == (db_conn = TALER_MINT_DB_get_connection (GNUNET_NO))) + if (NULL == (session = plugin->get_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } if (GNUNET_OK != - TALER_MINT_DB_transaction (db_conn)) + plugin->start (plugin->cls, + session)) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - res = TALER_MINT_DB_get_refresh_session (db_conn, - refresh_session_pub, - &session); + res = plugin->get_refresh_session (plugin->cls, + session, + refresh_session_pub, + &refresh_session); if (GNUNET_YES == res) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); res = TALER_MINT_reply_refresh_melt_success (connection, - &session.session_hash, - session.noreveal_index); + &refresh_session.session_hash, + refresh_session.noreveal_index); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } if (GNUNET_SYSERR == res) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } @@ -610,7 +640,7 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, { if (GNUNET_OK != (res = refresh_accept_melts (connection, - db_conn, + session, key_state, melt_hash, refresh_session_pub, @@ -619,7 +649,8 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, i))) { TALER_MINT_key_state_release (key_state); - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } } @@ -629,12 +660,14 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, for (i=0;iinsert_refresh_order (plugin->cls, + session, + refresh_session_pub, + i, + denom_pubs[i])) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } } @@ -644,13 +677,15 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, for (j = 0; j < num_new_denoms; j++) { if (GNUNET_OK != - TALER_MINT_DB_insert_refresh_commit_coin (db_conn, - refresh_session_pub, - i, - j, - &commit_coin[i][j])) + plugin->insert_refresh_commit_coin (plugin->cls, + session, + refresh_session_pub, + i, + j, + &commit_coin[i][j])) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } } @@ -660,13 +695,15 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, for (j = 0; j < coin_count; j++) { if (GNUNET_OK != - TALER_MINT_DB_insert_refresh_commit_link (db_conn, - refresh_session_pub, - i, - j, - &commit_link[i][j])) + plugin->insert_refresh_commit_link (plugin->cls, + session, + refresh_session_pub, + i, + j, + &commit_link[i][j])) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } } @@ -674,34 +711,37 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, /* store 'global' session data */ - session.melt_sig = *client_signature; - session.session_hash = *melt_hash; - session.num_oldcoins = coin_count; - session.num_newcoins = num_new_denoms; - session.kappa = KAPPA; // FIXME... - session.noreveal_index + refresh_session.melt_sig = *client_signature; + refresh_session.session_hash = *melt_hash; + refresh_session.num_oldcoins = coin_count; + refresh_session.num_newcoins = num_new_denoms; + refresh_session.kappa = KAPPA; // FIXME... + refresh_session.noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, - session.kappa); + refresh_session.kappa); if (GNUNET_OK != - (res = TALER_MINT_DB_create_refresh_session (db_conn, - refresh_session_pub, - &session))) + (res = plugin->create_refresh_session (plugin->cls, + session, + refresh_session_pub, + &refresh_session))) { - TALER_MINT_DB_rollback (db_conn); + plugin->rollback (plugin->cls, + session); return TALER_MINT_reply_internal_db_error (connection); } if (GNUNET_OK != - TALER_MINT_DB_commit (db_conn)) + plugin->commit (plugin->cls, + session)) { LOG_WARNING ("/refresh/melt transaction commit failed\n"); return TALER_MINT_reply_commit_error (connection); } return TALER_MINT_reply_refresh_melt_success (connection, - &session.session_hash, - session.noreveal_index); + &refresh_session.session_hash, + refresh_session.noreveal_index); } @@ -712,7 +752,7 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, * Then derives the shared secret for each kappa, and check that they match. * * @param connection the MHD connection to handle - * @param db_conn database connection to use + * @param session database connection to use * @param refresh_session session to query * @param off commitment offset to check * @param num_oldcoins size of the @a transfer_privs and @a melts arrays @@ -726,7 +766,7 @@ TALER_MINT_db_execute_refresh_melt (struct MHD_Connection *connection, */ static int check_commitment (struct MHD_Connection *connection, - PGconn *db_conn, + struct TALER_MINTDB_Session *session, const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, unsigned int off, unsigned int num_oldcoins, @@ -749,11 +789,12 @@ check_commitment (struct MHD_Connection *connection, struct TALER_LinkSecret shared_secret; struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub_check; - res = TALER_MINT_DB_get_refresh_commit_link (db_conn, - refresh_session, - off, - j, - &commit_link); + res = plugin->get_refresh_commit_link (plugin->cls, + session, + refresh_session, + off, + j, + &commit_link); if (GNUNET_OK != res) { GNUNET_break (0); @@ -842,11 +883,12 @@ check_commitment (struct MHD_Connection *connection, char *buf; size_t buf_len; - res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, - refresh_session, - off, - j, - &commit_coin); + res = plugin->get_refresh_commit_coin (plugin->cls, + session, + refresh_session, + off, + j, + &commit_coin); if (GNUNET_OK != res) { GNUNET_break (0); @@ -914,7 +956,7 @@ check_commitment (struct MHD_Connection *connection, * envelope from the database and performs the signing operation. * * @param connection the MHD connection to handle - * @param db_conn database connection to use + * @param session database connection to use * @param refresh_session session to query * @param key_state key state to lookup denomination pubs * @param denom_pub denomination key for the coin to create @@ -925,7 +967,7 @@ check_commitment (struct MHD_Connection *connection, */ static struct GNUNET_CRYPTO_rsa_Signature * refresh_mint_coin (struct MHD_Connection *connection, - PGconn *db_conn, + struct TALER_MINTDB_Session *session, const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, struct MintKeyState *key_state, const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub, @@ -937,11 +979,12 @@ refresh_mint_coin (struct MHD_Connection *connection, struct GNUNET_CRYPTO_rsa_Signature *ev_sig; int res; - res = TALER_MINT_DB_get_refresh_commit_coin (db_conn, - refresh_session, - noreveal_index, - coin_off, - &commit_coin); + res = plugin->get_refresh_commit_coin (plugin->cls, + session, + refresh_session, + noreveal_index, + coin_off, + &commit_coin); if (GNUNET_OK != res) { GNUNET_break (0); @@ -962,10 +1005,11 @@ refresh_mint_coin (struct MHD_Connection *connection, return NULL; } if (GNUNET_OK != - TALER_MINT_DB_insert_refresh_collectable (db_conn, - refresh_session, - coin_off, - ev_sig)) + plugin->insert_refresh_collectable (plugin->cls, + session, + refresh_session, + coin_off, + ev_sig)) { GNUNET_break (0); GNUNET_CRYPTO_rsa_signature_free (ev_sig); @@ -997,7 +1041,7 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, struct GNUNET_CRYPTO_EcdsaPrivateKey *const*transfer_privs) { int res; - PGconn *db_conn; + struct TALER_MINTDB_Session *session; struct RefreshSession refresh_session; struct MintKeyState *key_state; struct RefreshMelt *melts; @@ -1007,15 +1051,17 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, unsigned int j; unsigned int off; - if (NULL == (db_conn = TALER_MINT_DB_get_connection (GNUNET_NO))) + if (NULL == (session = plugin->get_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - res = TALER_MINT_DB_get_refresh_session (db_conn, - refresh_session_pub, - &refresh_session); + res = plugin->get_refresh_session (plugin->cls, + session, + refresh_session_pub, + &refresh_session); if (GNUNET_NO == res) return TALER_MINT_reply_arg_invalid (connection, "session_pub"); @@ -1032,10 +1078,11 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, for (j=0;jget_refresh_melt (plugin->cls, + session, + refresh_session_pub, + j, + &melts[j])) { GNUNET_break (0); GNUNET_free (melts); @@ -1046,9 +1093,10 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, sizeof (struct GNUNET_CRYPTO_rsa_PublicKey *)); for (j=0;jget_refresh_order (plugin->cls, + session, + refresh_session_pub, + j); if (NULL == denom_pubs[j]) { GNUNET_break (0); @@ -1069,7 +1117,7 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, off = 1; if (GNUNET_OK != (res = check_commitment (connection, - db_conn, + session, refresh_session_pub, i + off, refresh_session.num_oldcoins, @@ -1089,7 +1137,8 @@ TALER_MINT_db_execute_refresh_reveal (struct MHD_Connection *connection, /* Client request OK, start transaction */ if (GNUNET_OK != - TALER_MINT_DB_transaction (db_conn)) + plugin->start (plugin->cls, + session)) { GNUNET_break (0); for (j=0;jcommit (plugin->cls, + session)) { LOG_WARNING ("/refresh/reveal transaction commit failed\n"); for (i=0;iget_session (plugin->cls, + GNUNET_NO))) { GNUNET_break (0); return TALER_MINT_reply_internal_db_error (connection); } - res = TALER_db_get_transfer (db_conn, - coin_pub, - &transfer_pub, - &shared_secret_enc); + res = plugin->get_transfer (plugin->cls, + session, + coin_pub, + &transfer_pub, + &shared_secret_enc); if (GNUNET_SYSERR == res) { GNUNET_break (0); @@ -1190,7 +1242,9 @@ TALER_MINT_db_execute_refresh_link (struct MHD_Connection *connection, } GNUNET_assert (GNUNET_OK == res); - ldl = TALER_db_get_link (db_conn, coin_pub); + ldl = plugin->get_link (plugin->cls, + session, + coin_pub); if (NULL == ldl) { return TALER_MINT_reply_json_pack (connection, diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index a40e3ae2..dbfecccd 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -25,7 +25,7 @@ #include #include #include "taler_util.h" -#include "mint_db.h" +#include "taler_mintdb_plugin.h" /** diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c index 37d6d23d..e411e0d8 100644 --- a/src/mint/taler-mint-httpd_deposit.c +++ b/src/mint/taler-mint-httpd_deposit.c @@ -32,7 +32,7 @@ #include #include #include -#include "mint_db.h" +#include "taler_mintdb_plugin.h" #include "taler_signatures.h" #include "taler_util.h" #include "taler-mint-httpd_parsing.h" diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c index e62521ef..d4979288 100644 --- a/src/mint/taler-mint-httpd_refresh.c +++ b/src/mint/taler-mint-httpd_refresh.c @@ -24,7 +24,7 @@ #include #include #include -#include "mint_db.h" +#include "taler_mintdb_plugin.h" #include "taler_signatures.h" #include "taler_util.h" #include "taler-mint-httpd_parsing.h" diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c index df3f0cd5..958d234b 100644 --- a/src/mint/taler-mint-reservemod.c +++ b/src/mint/taler-mint-reservemod.c @@ -24,7 +24,7 @@ #include #include "taler_util.h" #include "taler_signatures.h" -#include "mint_db.h" +#include "taler_mintdb_plugin.h" #include "db_pq.h" diff --git a/src/mint/taler_mintdb_plugin.h b/src/mint/taler_mintdb_plugin.h new file mode 100644 index 00000000..d330b817 --- /dev/null +++ b/src/mint/taler_mintdb_plugin.h @@ -0,0 +1,1002 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see +*/ +/** + * @file mint/mint_db.h + * @brief Low-level (statement-level) database access for the mint + * @author Florian Dold + * @author Christian Grothoff + */ +#ifndef MINT_DB_H +#define MINT_DB_H + +#include +#include "taler_util.h" + + +/** + * Information we keep on bank transfer(s) that established a reserve. + */ +struct BankTransfer +{ + + /** + * Public key of the reserve that was filled. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + + /** + * Amount that was transferred to the mint. + */ + struct TALER_Amount amount; + + /** + * Detailed wire information about the transaction. + */ + const json_t *wire; + +}; + + +/** + * A summary of a Reserve + */ +struct Reserve +{ + /** + * The reserve's public key. This uniquely identifies the reserve + */ + struct GNUNET_CRYPTO_EddsaPublicKey *pub; + + /** + * The balance amount existing in the reserve + */ + struct TALER_Amount balance; + + /** + * The expiration date of this reserve + */ + struct GNUNET_TIME_Absolute expiry; +}; + + +/** + * Information we keep for a withdrawn coin to reproduce + * the /withdraw operation if needed, and to have proof + * that a reserve was drained by this amount. + */ +struct CollectableBlindcoin +{ + + /** + * Our signature over the (blinded) coin. + */ + struct GNUNET_CRYPTO_rsa_Signature *sig; + + /** + * Denomination key (which coin was generated). + */ + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + + /** + * Public key of the reserve that was drained. + */ + struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub; + + /** + * Hash over the blinded message, needed to verify + * the @e reserve_sig. + */ + struct GNUNET_HashCode h_coin_envelope; + + /** + * Signature confirming the withdrawl, matching @e reserve_pub, + * @e denom_pub and @e h_coin_envelope. + */ + struct GNUNET_CRYPTO_EddsaSignature reserve_sig; +}; + + + +/** + * Types of operations on a reserved. + */ +enum TALER_MINT_DB_ReserveOperation +{ + /** + * Money was deposited into the reserve via a bank transfer. + */ + TALER_MINT_DB_RO_BANK_TO_MINT = 0, + + /** + * A Coin was withdrawn from the reserve using /withdraw. + */ + TALER_MINT_DB_RO_WITHDRAW_COIN = 1 +}; + + +/** + * Reserve history as a linked list. Lists all of the transactions + * associated with this reserve (such as the bank transfers that + * established the reserve and all /withdraw operations we have done + * since). + */ +struct ReserveHistory +{ + + /** + * Next entry in the reserve history. + */ + struct ReserveHistory *next; + + /** + * Type of the event, determins @e details. + */ + enum TALER_MINT_DB_ReserveOperation type; + + /** + * Details of the operation, depending on @e type. + */ + union + { + + /** + * Details about a bank transfer to the mint. + */ + struct BankTransfer *bank; + + /** + * Details about a /withdraw operation. + */ + struct CollectableBlindcoin *withdraw; + + } details; + +}; + + +/** + * Specification for a /deposit operation. + */ +struct Deposit +{ + /** + * Information about the coin that is being deposited. + */ + struct TALER_CoinPublicInfo coin; + + /** + * ECDSA signature affirming that the customer intends + * this coin to be deposited at the merchant identified + * by @e h_wire in relation to the contract identified + * by @e h_contract. + */ + struct GNUNET_CRYPTO_EcdsaSignature csig; + + /** + * Public key of the merchant. Enables later identification + * of the merchant in case of a need to rollback transactions. + */ + struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub; + + /** + * Hash over the contract between merchant and customer + * (remains unknown to the Mint). + */ + struct GNUNET_HashCode h_contract; + + /** + * Hash of the (canonical) representation of @e wire, used + * to check the signature on the request. Generated by + * the mint from the detailed wire data provided by the + * merchant. + */ + struct GNUNET_HashCode h_wire; + + /** + * Detailed wire information for executing the transaction. + */ + const json_t *wire; + + /** + * Merchant-generated transaction ID to detect duplicate + * transactions. + */ + uint64_t transaction_id; + + /** + * Fraction of the coin's remaining value to be deposited. + * The coin is identified by @e coin_pub. + */ + struct TALER_Amount amount; + +}; + + +/** + * Global information for a refreshing session. Includes + * dimensions of the operation, security parameters and + * client signatures from "/refresh/melt" and "/refresh/commit". + */ +struct RefreshSession +{ + /** + * Signature over the commitments by the client, + * only valid if @e has_commit_sig is set. + */ + struct GNUNET_CRYPTO_EddsaSignature commit_sig; + + /** + * Hash over coins to melt and coins to create of the + * refresh session. + */ + struct GNUNET_HashCode session_hash; + + /** + * Signature over the melt by the client. + */ + struct GNUNET_CRYPTO_EddsaSignature melt_sig; + + /** + * Number of coins we are melting. + */ + uint16_t num_oldcoins; + + /** + * Number of new coins we are creating. + */ + uint16_t num_newcoins; + + /** + * Number of parallel operations we perform for the cut and choose. + * (must be greater or equal to three for security). 0 if not yet + * known. + */ + uint16_t kappa; + + /** + * Index (smaller @e kappa) which the mint has chosen to not + * have revealed during cut and choose. + */ + uint16_t noreveal_index; + +}; + + +/** + * Specification for coin in a /refresh/melt operation. + */ +struct RefreshMelt +{ + /** + * Information about the coin that is being melted. + */ + struct TALER_CoinPublicInfo coin; + + /** + * Signature over the melting operation. + */ + struct GNUNET_CRYPTO_EcdsaSignature coin_sig; + + /** + * Which melting operation should the coin become a part of. + */ + struct GNUNET_HashCode melt_hash; + + /** + * How much value is being melted? + * This amount includes the fees, so the final amount contributed + * to the melt is this value minus the fee for melting the coin. + */ + struct TALER_Amount amount; + +}; + + +/** + * We have as many `struct RefreshCommitCoin` as there are new + * coins being created by the refresh (for each of the kappa + * sets). These are the coins we ask the mint to sign if the + * respective set is selected. + */ +struct RefreshCommitCoin +{ + + /** + * Encrypted data allowing those able to decrypt it to derive + * the private keys of the new coins created by the refresh. + */ + struct TALER_RefreshLinkEncrypted *refresh_link; + + /** + * Blinded message to be signed (in envelope), with @e coin_env_size bytes. + */ + char *coin_ev; + + /** + * Number of bytes in @e coin_ev. + */ + size_t coin_ev_size; + +}; + + +/** + * For each (old) coin being melted, we have a `struct + * RefreshCommitLink` that allows the user to find the shared secret + * to decrypt the respective refresh links for the new coins in the + * `struct RefreshCommitCoin`. + */ +struct RefreshCommitLink +{ + /** + * Transfer public key (FIXME: explain!) + */ + struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub; + + /** + * Encrypted shared secret to decrypt the link. + */ + struct TALER_EncryptedLinkSecret shared_secret_enc; +}; + + +/** + * Linked list of refresh information linked to a coin. + */ +struct LinkDataList +{ + /** + * Information is stored in a NULL-terminated linked list. + */ + struct LinkDataList *next; + + /** + * Link data, used to recover the private key of the coin + * by the owner of the old coin. + */ + struct TALER_RefreshLinkEncrypted *link_data_enc; + + /** + * Denomination public key, determines the value of the coin. + */ + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + + /** + * Signature over the blinded envelope. + */ + struct GNUNET_CRYPTO_rsa_Signature *ev_sig; +}; + + +/** + * Specification for a /lock operation. + */ +struct Lock +{ + /** + * Information about the coin that is being melted. + */ + struct TALER_CoinPublicInfo coin; + + /** + * Signature over the melting operation. + */ + const struct GNUNET_CRYPTO_EcdsaSignature coin_sig; + + /** + * How much value is being melted? + */ + struct TALER_Amount amount; + + // FIXME: more needed... +}; + + +/** + * Enumeration to classify the different types of transactions + * that can be done with a coin. + */ +enum TALER_MINT_DB_TransactionType +{ + /** + * /deposit operation. + */ + TALER_MINT_DB_TT_DEPOSIT = 0, + + /** + * /refresh/melt operation. + */ + TALER_MINT_DB_TT_REFRESH_MELT = 1, + + /** + * /lock operation. + */ + TALER_MINT_DB_TT_LOCK = 2 +}; + + +/** + * List of transactions we performed for a particular coin. + */ +struct TALER_MINT_DB_TransactionList +{ + + /** + * Next pointer in the NULL-terminated linked list. + */ + struct TALER_MINT_DB_TransactionList *next; + + /** + * Type of the transaction, determines what is stored in @e details. + */ + enum TALER_MINT_DB_TransactionType type; + + /** + * Details about the transaction, depending on @e type. + */ + union + { + + /** + * Details if transaction was a /deposit operation. + */ + struct Deposit *deposit; + + /** + * Details if transaction was a /refresh/melt operation. + */ + struct RefreshMelt *melt; + + /** + * Details if transaction was a /lock operation. + */ + struct Lock *lock; + + } details; + +}; + + +/** + * Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session; + + +/** + * The plugin API, returned from the plugin's "init" function. + * The argument given to "init" is simply a configuration handle. + */ +struct TALER_MINTDB_Plugin +{ + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + * database default one + * @param the database connection, or NULL on error + */ + struct TALER_MINTDB_Session * + (*get_session) (void *cls, + int temporary); + + + /** + * Drop the temporary taler schema. This is only useful for testcases. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*drop_temporary) (void *cls, + struct TALER_MINTDB_Session *db); + + + /** + * Create the necessary tables if they are not present + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*create_tables) (void *cls, + int temporary); + + + /** + * Start a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn connection to use + * @return #GNUNET_OK on success + */ + int + (*start) (void *cls, + struct TALER_MINTDB_Session *db_conn); + + + /** + * Commit a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn connection to use + * @return #GNUNET_OK on success + */ + int + (*commit) (void *cls, + struct TALER_MINTDB_Session *db_conn); + + + /** + * Abort/rollback a transaction. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn connection to use + */ + void + (*rollback) (void *cls, + struct TALER_MINTDB_Session *db_conn); + + + /** + * Get the summary of a reserve. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db the database connection handle + * @param reserve the reserve data. The public key of the reserve should be set + * in this structure; it is used to query the database. The balance + * and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*reserve_get) (void *cls, + struct TALER_MINTDB_Session *db, + struct Reserve *reserve); + + /* FIXME: add functions to add bank transfers to our DB + (and to test if we already did add one) (#3633/#3717) */ + + + /** + * Insert a incoming transaction into reserves. New reserves are also created + * through this function. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db the database connection handle + * @param reserve the reserve structure. The public key of the reserve should + * be set here. Upon successful execution of this function, the + * balance and expiration of the reserve will be updated. + * @param balance the amount that has to be added to the reserve + * @param expiry the new expiration time for the reserve + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures + */ + int + (*reserves_in_insert) (void *cls, + struct TALER_MINTDB_Session *db, + struct Reserve *reserve, + const struct TALER_Amount *balance, + const struct GNUNET_TIME_Absolute expiry); + + + /** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param h_blind hash of the blinded message + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ + int + (*get_collectable_blindcoin) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_HashCode *h_blind, + struct CollectableBlindcoin *collectable); + + + /** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ + int + (*insert_collectable_blindcoin) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, + const struct CollectableBlindcoin *collectable); + + + /** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ + struct ReserveHistory * + (*get_reserve_history) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub); + + + /** + * Check if we have the specified deposit already in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + * #GNUNET_NO if this deposit is unknown to us, + * #GNUNET_SYSERR on internal error + */ + int + (*have_deposit) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct Deposit *deposit); + + + /** + * Insert information about deposited coin into the + * database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ + int + (*insert_deposit) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct Deposit *deposit); + + + /** + * Lookup refresh session data under the given public key. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database handle to use + * @param refresh_session_pub public key to use for the lookup + * @param refresh_session[OUT] where to store the result + * @return #GNUNET_YES on success, + * #GNUNET_NO if not found, + * #GNUNET_SYSERR on DB failure + */ + int + (*get_refresh_session) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + struct RefreshSession *refresh_session); + + + /** + * Store new refresh session data under the given public key. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database handle to use + * @param refresh_session_pub public key to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + * #GNUNET_SYSERR on DB failure + */ + int + (*create_refresh_session) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + const struct RefreshSession *refresh_session); + + + + /** + * Store the given /refresh/melt request in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to store + * @param melt coin melt operation details to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_refresh_melt) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, + uint16_t oldcoin_index, + const struct RefreshMelt *melt); + + + /** + * Get information about melted coin details from the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*get_refresh_melt) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session, + uint16_t oldcoin_index, + struct RefreshMelt *melt); + + + /** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param session_pub refresh session key + * @param newcoin_index index of the coin to generate + * @param denom_pub denomination of the coin to create + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_refresh_order) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub); + + + /** + * Lookup in the database the @a newcoin_index coin that we want to + * create in the given refresh operation. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param session_pub refresh session key + * @param newcoin_index index of the coin to generate + * @param denom_pub denomination of the coin to create + * @return NULL on error (not found or internal error) + */ + struct GNUNET_CRYPTO_rsa_PublicKey * + (*get_refresh_order) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index); + + + /** + * Store information about the commitment of the + * given coin for the given refresh session in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param refresh_session_pub refresh session this commitment belongs to + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin coin commitment to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error + */ + int + (*insert_refresh_commit_coin) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + const struct RefreshCommitCoin *commit_coin); + + + /** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param refresh_session_pub refresh session the commitment belongs to + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin[OUT] coin commitment to return + * @return #GNUNET_OK on success + * #GNUNET_NO if not found + * #GNUNET_SYSERR on error + */ + int + (*get_refresh_commit_coin) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + struct RefreshCommitCoin *commit_coin); + + + /** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param refresh_session_pub public key of the refresh session this + * commitment belongs with + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param commit_link link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ + int + (*insert_refresh_commit_link) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + const struct RefreshCommitLink *commit_link); + + /** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection to use + * @param refresh_session_pub public key of the refresh session this + * commitment belongs with + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param cc[OUT] link information to return + * @return #GNUNET_SYSERR on internal error, + * #GNUNET_NO if commitment was not found + * #GNUNET_OK on success + */ + int + (*get_refresh_commit_link) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub, + unsigned int i, + unsigned int j, + struct RefreshCommitLink *cc); + + + /** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param session_pub refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ + int + (*insert_refresh_collectable) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub, + uint16_t newcoin_index, + const struct GNUNET_CRYPTO_rsa_Signature *ev_sig); + + + /** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param coin_pub public key to use to retrieve linkage data + * @return all known link data for the coin + */ + struct LinkDataList * + (*get_link) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); + + + /** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * #TALER_db_get_link() enable the owner of an old coin to determine + * the private keys of the new coins after the melt. + * + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param coin_pub public key of the coin + * @param transfer_pub[OUT] public transfer key + * @param shared_secret_enc[OUT] set to shared secret + * @return #GNUNET_OK on success, + * #GNUNET_NO on failure (not found) + * #GNUNET_SYSERR on internal failure (database issue) + */ + int + (*get_transfer) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub, + struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub, + struct TALER_EncryptedLinkSecret *shared_secret_enc); + + + /** + * Test if the given /lock request is known to us. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param lock lock operation + * @return #GNUNET_YES if known, + * #GNUENT_NO if not, + * #GNUNET_SYSERR on internal error + */ + int + (*have_lock) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct Lock *lock); + + + /** + * Store the given /lock request in the database. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param lock lock operation + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ + int + (*insert_lock) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct Lock *lock); + + + /** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param db_conn database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ + struct TALER_MINT_DB_TransactionList * + (*get_coin_transactions) (void *cls, + struct TALER_MINTDB_Session *db_conn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub); + +}; + + +#endif /* _NEURO_MINT_DB_H */ diff --git a/src/util/Makefile.am b/src/util/Makefile.am index a15d42ad..07c120c5 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -8,7 +8,8 @@ libtalerutil_la_SOURCES = \ amount.c \ crypto.c \ util.c \ - json.c + json.c \ + os_installation.c libtalerutil_la_LIBADD = \ -lgnunetutil \ diff --git a/src/util/os_installation.c b/src/util/os_installation.c new file mode 100644 index 00000000..82dc4918 --- /dev/null +++ b/src/util/os_installation.c @@ -0,0 +1,701 @@ +/* + This file is part of GNUnet. + Copyright (C) 2006-2014 Christian Grothoff (and other contributing authors) + + GNUnet 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. + + GNUnet 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 GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file os_installation.c + * @brief get paths used by the program; based heavily on the + * corresponding GNUnet file, just adapted for Taler. + * @author Milan + */ +#include "platform.h" +#include +#if DARWIN +#include +#include +#elif WINDOWS +#include +#endif + + +#define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__) + +#define LOG_STRERROR_FILE(kind,syscall,filename) GNUNET_log_from_strerror_file (kind, "util", syscall, filename) + + +#if LINUX +/** + * Try to determine path by reading /proc/PID/exe + * + * @return NULL on error + */ +static char * +get_path_from_proc_maps () +{ + char fn[64]; + char line[1024]; + char dir[1024]; + FILE *f; + char *lgu; + + GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/maps", getpid ()); + if (NULL == (f = FOPEN (fn, "r"))) + return NULL; + while (NULL != fgets (line, sizeof (line), f)) + { + if ((1 == + SSCANF (line, "%*x-%*x %*c%*c%*c%*c %*x %*2x:%*2x %*u%*[ ]%1023s", dir)) && + (NULL != (lgu = strstr (dir, "libtalerutil")))) + { + lgu[0] = '\0'; + FCLOSE (f); + return GNUNET_strdup (dir); + } + } + FCLOSE (f); + return NULL; +} + + +/** + * Try to determine path by reading /proc/PID/exe + * + * @return NULL on error + */ +static char * +get_path_from_proc_exe () +{ + char fn[64]; + char lnk[1024]; + ssize_t size; + + GNUNET_snprintf (fn, sizeof (fn), "/proc/%u/exe", getpid ()); + size = readlink (fn, lnk, sizeof (lnk) - 1); + if (size <= 0) + { + LOG_STRERROR_FILE (GNUNET_ERROR_TYPE_ERROR, "readlink", fn); + return NULL; + } + GNUNET_assert (size < sizeof (lnk)); + lnk[size] = '\0'; + while ((lnk[size] != '/') && (size > 0)) + size--; + /* test for being in lib/taler/libexec/ or lib/MULTIARCH/taler/libexec */ + if ( (size > strlen ("/taler/libexec/")) && + (0 == strcmp ("/taler/libexec/", + &lnk[size - strlen ("/taler/libexec/")])) ) + size -= strlen ("taler/libexec/"); + if ((size < 4) || (lnk[size - 4] != '/')) + { + /* not installed in "/bin/" -- binary path probably useless */ + return NULL; + } + lnk[size] = '\0'; + return GNUNET_strdup (lnk); +} +#endif + + +#if WINDOWS +static HINSTANCE dll_instance; + + +/** + * GNUNET_util_cl_init() in common_logging.c is preferred. + * This function is only for thread-local storage (not used in GNUnet) + * and hInstance saving. + */ +BOOL WINAPI +DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + dll_instance = hinstDLL; + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + + +/** + * Try to determine path with win32-specific function + * + * @return NULL on error + */ +static char * +get_path_from_module_filename () +{ + size_t pathlen = 512; + DWORD real_pathlen; + wchar_t *idx; + wchar_t *modulepath = NULL; + char *upath; + uint8_t *u8_string; + size_t u8_string_length; + + /* This braindead function won't tell us how much space it needs, so + * we start at 1024 and double the space up if it doesn't fit, until + * it fits, or we exceed the threshold. + */ + do + { + pathlen = pathlen * 2; + modulepath = GNUNET_realloc (modulepath, pathlen * sizeof (wchar_t)); + SetLastError (0); + real_pathlen = GetModuleFileNameW (dll_instance, modulepath, pathlen * sizeof (wchar_t)); + } while (real_pathlen >= pathlen && pathlen < 16*1024); + if (real_pathlen >= pathlen) + GNUNET_assert (0); + /* To be safe */ + modulepath[real_pathlen] = '\0'; + + idx = modulepath + real_pathlen; + while ((idx > modulepath) && (*idx != L'\\') && (*idx != L'/')) + idx--; + *idx = L'\0'; + + /* Now modulepath holds full path to the directory where libtalerutil is. + * This directory should look like /bin or . + */ + if (wcschr (modulepath, L'/') || wcschr (modulepath, L'\\')) + { + /* At least one directory component (i.e. we're not in a root directory) */ + wchar_t *dirname = idx; + while ((dirname > modulepath) && (*dirname != L'\\') && (*dirname != L'/')) + dirname--; + *dirname = L'\0'; + if (dirname > modulepath) + { + dirname++; + /* Now modulepath holds full path to the parent directory of the directory + * where libtalerutil is. + * dirname holds the name of the directory where libtalerutil is. + */ + if (wcsicmp (dirname, L"bin") == 0) + { + /* pass */ + } + else + { + /* Roll back our changes to modulepath */ + dirname--; + *dirname = L'/'; + } + } + } + + /* modulepath is TALER_PREFIX */ + u8_string = u16_to_u8 (modulepath, wcslen (modulepath), NULL, &u8_string_length); + if (NULL == u8_string) + GNUNET_assert (0); + + upath = GNUNET_malloc (u8_string_length + 1); + memcpy (upath, u8_string, u8_string_length); + upath[u8_string_length] = '\0'; + + free (u8_string); + GNUNET_free (modulepath); + + return upath; +} +#endif + + +#if DARWIN +/** + * Signature of the '_NSGetExecutablePath" function. + * + * @param buf where to write the path + * @param number of bytes available in 'buf' + * @return 0 on success, otherwise desired number of bytes is stored in 'bufsize' + */ +typedef int (*MyNSGetExecutablePathProto) (char *buf, size_t * bufsize); + + +/** + * Try to obtain the path of our executable using '_NSGetExecutablePath'. + * + * @return NULL on error + */ +static char * +get_path_from_NSGetExecutablePath () +{ + static char zero = '\0'; + char *path; + size_t len; + MyNSGetExecutablePathProto func; + + path = NULL; + if (NULL == (func = + (MyNSGetExecutablePathProto) dlsym (RTLD_DEFAULT, "_NSGetExecutablePath"))) + return NULL; + path = &zero; + len = 0; + /* get the path len, including the trailing \0 */ + (void) func (path, &len); + if (0 == len) + return NULL; + path = GNUNET_malloc (len); + if (0 != func (path, &len)) + { + GNUNET_free (path); + return NULL; + } + len = strlen (path); + while ((path[len] != '/') && (len > 0)) + len--; + path[len] = '\0'; + return path; +} + + +/** + * Try to obtain the path of our executable using '_dyld_image' API. + * + * @return NULL on error + */ +static char * +get_path_from_dyld_image () +{ + const char *path; + char *p; + char *s; + unsigned int i; + int c; + + c = _dyld_image_count (); + for (i = 0; i < c; i++) + { + if (((const void *) _dyld_get_image_header (i)) != (const void *)&_mh_dylib_header) + continue; + path = _dyld_get_image_name (i); + if ( (NULL == path) || (0 == strlen (path)) ) + continue; + p = GNUNET_strdup (path); + s = p + strlen (p); + while ((s > p) && ('/' != *s)) + s--; + s++; + *s = '\0'; + return p; + } + return NULL; +} +#endif + + +/** + * Return the actual path to a file found in the current + * PATH environment variable. + * + * @param binary the name of the file to find + * @return path to binary, NULL if not found + */ +static char * +get_path_from_PATH (const char *binary) +{ + char *path; + char *pos; + char *end; + char *buf; + const char *p; + + if (NULL == (p = getenv ("PATH"))) + return NULL; +#if WINDOWS + /* On W32 look in CWD first. */ + GNUNET_asprintf (&path, ".%c%s", PATH_SEPARATOR, p); +#else + path = GNUNET_strdup (p); /* because we write on it */ +#endif + buf = GNUNET_malloc (strlen (path) + strlen (binary) + 1 + 1); + pos = path; + while (NULL != (end = strchr (pos, PATH_SEPARATOR))) + { + *end = '\0'; + sprintf (buf, "%s/%s", pos, binary); + if (GNUNET_DISK_file_test (buf) == GNUNET_YES) + { + pos = GNUNET_strdup (pos); + GNUNET_free (buf); + GNUNET_free (path); + return pos; + } + pos = end + 1; + } + sprintf (buf, "%s/%s", pos, binary); + if (GNUNET_YES == GNUNET_DISK_file_test (buf)) + { + pos = GNUNET_strdup (pos); + GNUNET_free (buf); + GNUNET_free (path); + return pos; + } + GNUNET_free (buf); + GNUNET_free (path); + return NULL; +} + + +/** + * Try to obtain the installation path using the "TALER_PREFIX" environment + * variable. + * + * @return NULL on error (environment variable not set) + */ +static char * +get_path_from_TALER_PREFIX () +{ + const char *p; + + if (NULL != (p = getenv ("TALER_PREFIX"))) + return GNUNET_strdup (p); + return NULL; +} + + +/** + * @brief get the path to Taler bin/ or lib/, prefering the lib/ path + * @author Milan + * + * @return a pointer to the executable path, or NULL on error + */ +static char * +os_get_taler_path () +{ + char *ret; + + if (NULL != (ret = get_path_from_TALER_PREFIX ())) + return ret; +#if LINUX + if (NULL != (ret = get_path_from_proc_maps ())) + return ret; + /* try path *first*, before /proc/exe, as /proc/exe can be wrong */ + if (NULL != (ret = get_path_from_PATH ("taler-mint-httpd"))) + return ret; + if (NULL != (ret = get_path_from_proc_exe ())) + return ret; +#endif +#if WINDOWS + if (NULL != (ret = get_path_from_module_filename ())) + return ret; +#endif +#if DARWIN + if (NULL != (ret = get_path_from_dyld_image ())) + return ret; + if (NULL != (ret = get_path_from_NSGetExecutablePath ())) + return ret; +#endif + if (NULL != (ret = get_path_from_PATH ("taler-mint-httpd"))) + return ret; + /* other attempts here */ + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Could not determine installation path for %s. Set `%s' environment variable.\n"), + "Taler", "TALER_PREFIX"); + return NULL; +} + + +/** + * @brief get the path to current app's bin/ + * @author Milan + * + * @return a pointer to the executable path, or NULL on error + */ +static char * +os_get_exec_path () +{ + char *ret = NULL; + +#if LINUX + if (NULL != (ret = get_path_from_proc_exe ())) + return ret; +#endif +#if WINDOWS + if (NULL != (ret = get_path_from_module_filename ())) + return ret; +#endif +#if DARWIN + if (NULL != (ret = get_path_from_NSGetExecutablePath ())) + return ret; +#endif + /* other attempts here */ + return ret; +} + + +/** + * @brief get the path to a specific Taler installation directory or, + * with #TALER_OS_IPK_SELF_PREFIX, the current running apps installation directory + * @author Milan + * @return a pointer to the dir path (to be freed by the caller) + */ +char * +TALER_OS_installation_get_path (enum GNUNET_OS_InstallationPathKind dirkind) +{ + size_t n; + const char *dirname; + char *execpath = NULL; + char *tmp; + char *multiarch; + char *libdir; + int isbasedir; + + /* if wanted, try to get the current app's bin/ */ + if (dirkind == GNUNET_OS_IPK_SELF_PREFIX) + execpath = os_get_exec_path (); + + /* try to get Taler's bin/ or lib/, or if previous was unsuccessful some + * guess for the current app */ + if (NULL == execpath) + execpath = os_get_taler_path (); + + if (NULL == execpath) + return NULL; + + n = strlen (execpath); + if (0 == n) + { + /* should never happen, but better safe than sorry */ + GNUNET_free (execpath); + return NULL; + } + /* remove filename itself */ + while ((n > 1) && (DIR_SEPARATOR == execpath[n - 1])) + execpath[--n] = '\0'; + + isbasedir = 1; + if ((n > 6) && + ((0 == strcasecmp (&execpath[n - 6], "/lib32")) || + (0 == strcasecmp (&execpath[n - 6], "/lib64")))) + { + if ( (GNUNET_OS_IPK_LIBDIR != dirkind) && + (GNUNET_OS_IPK_LIBEXECDIR != dirkind) ) + { + /* strip '/lib32' or '/lib64' */ + execpath[n - 6] = '\0'; + n -= 6; + } + else + isbasedir = 0; + } + else if ((n > 4) && + ((0 == strcasecmp (&execpath[n - 4], "/bin")) || + (0 == strcasecmp (&execpath[n - 4], "/lib")))) + { + /* strip '/bin' or '/lib' */ + execpath[n - 4] = '\0'; + n -= 4; + } + multiarch = NULL; + if (NULL != (libdir = strstr (execpath, "/lib/"))) + { + /* test for multi-arch path of the form "PREFIX/lib/MULTIARCH/"; + here we need to re-add 'multiarch' to lib and libexec paths later! */ + multiarch = &libdir[5]; + if (NULL == strchr (multiarch, '/')) + libdir[0] = '\0'; /* Debian multiarch format, cut of from 'execpath' but preserve in multicarch */ + else + multiarch = NULL; /* maybe not, multiarch still has a '/', which is not OK */ + } + /* in case this was a directory named foo-bin, remove "foo-" */ + while ((n > 1) && (execpath[n - 1] == DIR_SEPARATOR)) + execpath[--n] = '\0'; + switch (dirkind) + { + case GNUNET_OS_IPK_PREFIX: + case GNUNET_OS_IPK_SELF_PREFIX: + dirname = DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_BINDIR: + dirname = DIR_SEPARATOR_STR "bin" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_LIBDIR: + if (isbasedir) + { + GNUNET_asprintf (&tmp, + "%s%s%s%s%s", + execpath, + DIR_SEPARATOR_STR "lib", + (NULL != multiarch) ? DIR_SEPARATOR_STR : "", + (NULL != multiarch) ? multiarch : "", + DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR); + if (GNUNET_YES == + GNUNET_DISK_directory_test (tmp, GNUNET_YES)) + { + GNUNET_free (execpath); + return tmp; + } + GNUNET_free (tmp); + tmp = NULL; + if (4 == sizeof (void *)) + { + dirname = + DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR; + GNUNET_asprintf (&tmp, + "%s%s", + execpath, + dirname); + } + if (8 == sizeof (void *)) + { + dirname = + DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR; + GNUNET_asprintf (&tmp, + "%s%s", + execpath, + dirname); + } + + if ( (NULL != tmp) && + (GNUNET_YES == + GNUNET_DISK_directory_test (tmp, GNUNET_YES)) ) + { + GNUNET_free (execpath); + return tmp; + } + GNUNET_free (tmp); + } + dirname = DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_DATADIR: + dirname = + DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_LOCALEDIR: + dirname = + DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "locale" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_ICONDIR: + dirname = + DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "icons" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_DOCDIR: + dirname = + DIR_SEPARATOR_STR "share" DIR_SEPARATOR_STR "doc" DIR_SEPARATOR_STR \ + "gnunet" DIR_SEPARATOR_STR; + break; + case GNUNET_OS_IPK_LIBEXECDIR: + if (isbasedir) + { + dirname = + DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR "libexec" DIR_SEPARATOR_STR; + GNUNET_asprintf (&tmp, + "%s%s%s%s", + execpath, + DIR_SEPARATOR_STR "lib" DIR_SEPARATOR_STR, + (NULL != multiarch) ? multiarch : "", + dirname); + if (GNUNET_YES == + GNUNET_DISK_directory_test (tmp, GNUNET_YES)) + { + GNUNET_free (execpath); + return tmp; + } + GNUNET_free (tmp); + tmp = NULL; + if (4 == sizeof (void *)) + { + dirname = + DIR_SEPARATOR_STR "lib32" DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR \ + "libexec" DIR_SEPARATOR_STR; + GNUNET_asprintf (&tmp, + "%s%s", + execpath, + dirname); + } + if (8 == sizeof (void *)) + { + dirname = + DIR_SEPARATOR_STR "lib64" DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR \ + "libexec" DIR_SEPARATOR_STR; + GNUNET_asprintf (&tmp, + "%s%s", + execpath, + dirname); + } + if ( (NULL != tmp) && + (GNUNET_YES == + GNUNET_DISK_directory_test (tmp, GNUNET_YES)) ) + { + GNUNET_free (execpath); + return tmp; + } + + GNUNET_free (tmp); + } + dirname = + DIR_SEPARATOR_STR "taler" DIR_SEPARATOR_STR \ + "libexec" DIR_SEPARATOR_STR; + break; + default: + GNUNET_free (execpath); + return NULL; + } + GNUNET_asprintf (&tmp, + "%s%s", + execpath, + dirname); + GNUNET_free (execpath); + return tmp; +} + + +/** + * Given the name of a taler-helper, taler-service or taler-daemon + * binary, try to prefix it with the libexec/-directory to get the + * full path. + * + * @param progname name of the binary + * @return full path to the binary, if possible, otherwise copy of 'progname' + */ +char * +TALER_OS_get_libexec_binary_path (const char *progname) +{ + static char *cache; + char *libexecdir; + char *binary; + + if ( (DIR_SEPARATOR == progname[0]) || + (GNUNET_YES == GNUNET_STRINGS_path_is_absolute (progname, GNUNET_NO, NULL, NULL)) ) + return GNUNET_strdup (progname); + if (NULL != cache) + libexecdir = cache; + else + libexecdir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBEXECDIR); + if (NULL == libexecdir) + return GNUNET_strdup (progname); + GNUNET_asprintf (&binary, + "%s%s", + libexecdir, + progname); + cache = libexecdir; + return binary; +} + + + +/* end of os_installation.c */ -- cgit v1.2.3