From 50749c462b55b05d42ca518572f75101401089d5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 19 Jun 2021 08:36:17 +0200 Subject: make libfakebank scale --- src/bank-lib/Makefile.am | 1 + src/bank-lib/fakebank.c | 1112 +++++++++++++++++++++++++------------ src/bank-lib/taler-fakebank-run.c | 12 + src/include/taler_fakebank_lib.h | 11 + 4 files changed, 779 insertions(+), 357 deletions(-) (limited to 'src') diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 5766cdfeb..a520b24df 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -67,4 +67,5 @@ libtalerfakebank_la_LIBADD = \ -ljansson \ $(LIBGNURLCURL_LIBS) \ -lmicrohttpd \ + -lpthread \ $(XLIB) diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index 51a324d42..e091adf54 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -21,7 +21,13 @@ * @brief library that fakes being a Taler bank for testcases * @author Christian Grothoff */ +// TOOD: pass test suite... +// TODO: support long polling +// TODO: support adding WAD transfers +// TODO: adapt taler-exchange-benchmark to profile bank API + #include "platform.h" +#include #include "taler_fakebank_lib.h" #include "taler_bank_service.h" #include "taler_mhd_lib.h" @@ -32,6 +38,68 @@ */ #define REQUEST_BUFFER_MAX (4 * 1024) +/** + * How long are exchange base URLs allowed to be at most? + * Set to a relatively low number as this does contribute + * significantly to our RAM consumption. + */ +#define MAX_URL_LEN 64 + +/** + * Details about a transcation we (as the simulated bank) received. + */ +struct Transaction; + +/** + * Per account information. + */ +struct Account +{ + + /** + * Inbound transactions for this account in a MDLL. + */ + struct Transaction *in_head; + + /** + * Inbound transactions for this account in a MDLL. + */ + struct Transaction *in_tail; + + /** + * Outbound transactions for this account in a MDLL. + */ + struct Transaction *out_head; + + /** + * Outbound transactions for this account in a MDLL. + */ + struct Transaction *out_tail; + + /** + * Account name (string, not payto!) + */ + char *account_name; + + /** + * Lock for modifying transaction list of this account. + * Note that per-transaction locks MUST be acquired before + * per-account locks (if both are acquired). + */ + pthread_mutex_t lock; + + /** + * Current account balance. + */ + struct TALER_Amount balance; + + /** + * true if the balance is negative. + */ + bool is_negative; + +}; + /** * Details about a transcation we (as the simulated bank) received. @@ -39,14 +107,24 @@ struct Transaction { /** - * We store transactions in a DLL. + * We store inbound transactions in a MDLL. + */ + struct Transaction *next_in; + + /** + * We store inbound transactions in a MDLL. + */ + struct Transaction *prev_in; + + /** + * We store outbound transactions in a MDLL. */ - struct Transaction *next; + struct Transaction *next_out; /** - * We store transactions in a DLL. + * We store outbound transactions in a MDLL. */ - struct Transaction *prev; + struct Transaction *prev_out; /** * Amount to be transferred. @@ -54,20 +132,36 @@ struct Transaction struct TALER_Amount amount; /** - * Account to debit (string, not payto!) + * Account to debit. */ - char *debit_account; + struct Account *debit_account; /** - * Account to credit (string, not payto!) + * Account to credit. */ - char *credit_account; + struct Account *credit_account; /** * Random unique identifier for the request. + * Used to detect idempotent requests. */ struct GNUNET_HashCode request_uid; + /** + * When did the transaction happen? + */ + struct GNUNET_TIME_Absolute date; + + /** + * Number of this transaction. + */ + uint64_t row_id; + + /** + * Lock for accessing this transaction array entry. + */ + pthread_mutex_t lock; + /** * What does the @e subject contain? */ @@ -109,7 +203,7 @@ struct Transaction /** * Base URL of the exchange. */ - char *exchange_base_url; + char exchange_base_url[MAX_URL_LEN]; } debit; @@ -140,29 +234,18 @@ struct Transaction /** * Base URL of the originating exchange. */ - char *origin_base_url; + char origin_base_url[MAX_URL_LEN]; } wad; } subject; /** - * When did the transaction happen? + * Has this transaction not yet been subjected to + * #TALER_FAKEBANK_check_credit() or #TALER_FAKEBANK_check_debit() and + * should thus be counted in #TALER_FAKEBANK_check_empty()? */ - struct GNUNET_TIME_Absolute date; - - /** - * Number of this transaction. - */ - uint64_t row_id; - - /** - * Has this transaction been subjected to #TALER_FAKEBANK_check_credit() - * or #TALER_FAKEBANK_check_debit() - * and should thus no longer be counted in - * #TALER_FAKEBANK_check_empty()? - */ - int checked; + bool unchecked; }; @@ -172,14 +255,9 @@ struct Transaction struct TALER_FAKEBANK_Handle { /** - * We store transactions in a DLL. - */ - struct Transaction *transactions_head; - - /** - * We store transactions in a DLL. + * We store transactions in a revolving array. */ - struct Transaction *transactions_tail; + struct Transaction *transactions; /** * HTTP server we run to pretend to be the "test" bank. @@ -187,7 +265,8 @@ struct TALER_FAKEBANK_Handle struct MHD_Daemon *mhd_bank; /** - * Task running HTTP server for the "test" bank. + * Task running HTTP server for the "test" bank, + * unless we are using a thread pool (then NULL). */ struct GNUNET_SCHEDULER_Task *mhd_task; @@ -199,10 +278,40 @@ struct TALER_FAKEBANK_Handle struct GNUNET_CONTAINER_MultiPeerMap *rpubs; /** - * Number of transactions. + * Lock for accessing @a rpubs map. + */ + pthread_mutex_t rpubs_lock; + + /** + * Hashmap of hashes of account names to `struct Account`. + */ + struct GNUNET_CONTAINER_MultiHashMap *accounts; + + /** + * Lock for accessing @a accounts hash map. + */ + pthread_mutex_t accounts_lock; + + /** + * Hashmap of hashes of transaction request_uids to `struct Transaction`. + */ + struct GNUNET_CONTAINER_MultiHashMap *uuid_map; + + /** + * Lock for accessing @a uuid_map. + */ + pthread_mutex_t uuid_map_lock; + + /** + * Current transaction counter. */ uint64_t serial_counter; + /** + * Number of transactions we keep in memory (at most). + */ + uint64_t ram_limit; + /** * Currency used by the fakebank. */ @@ -237,6 +346,59 @@ struct TALER_FAKEBANK_Handle }; +/** + * Lookup account with @a name, and if it does not exist, create it. + * + * @param[in,out] bank to lookup account at + * @param name account name to resolve + * @return account handle (never NULL) + */ +static struct Account * +lookup_account (struct TALER_FAKEBANK_Handle *h, + const char *name) +{ + struct GNUNET_HashCode hc; + size_t slen; + struct Account *account; + + memset (&hc, + 0, + sizeof (hc)); + slen = strlen (name); + if (slen < sizeof (hc)) + memcpy (&hc, + name, + slen); /* fake hashing for speed! */ + else + GNUNET_CRYPTO_hash (name, + strlen (name), + &hc); + GNUNET_assert (0 == + pthread_mutex_lock (&h->accounts_lock)); + account = GNUNET_CONTAINER_multihashmap_get (h->accounts, + &hc); + if (NULL == account) + { + account = GNUNET_new (struct Account); + GNUNET_assert (0 == + pthread_mutex_init (&account->lock, + NULL)); + account->account_name = GNUNET_strdup (name); + GNUNET_assert (GNUNET_OK == + TALER_amount_get_zero (h->currency, + &account->balance)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (h->accounts, + &hc, + account, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&h->accounts_lock)); + return account; +} + + /** * Generate log messages for failed check operation. * @@ -245,19 +407,19 @@ struct TALER_FAKEBANK_Handle static void check_log (struct TALER_FAKEBANK_Handle *h) { - for (struct Transaction *t = h->transactions_head; - NULL != t; - t = t->next) + for (uint64_t i = 0; iram_limit; i++) { - if (GNUNET_YES == t->checked) + struct Transaction *t = &h->transactions[i]; + + if (t->unchecked) continue; switch (t->type) { case T_DEBIT: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s -> %s (%s) %s (%s)\n", - t->debit_account, - t->credit_account, + t->debit_account->account_name, + t->credit_account->account_name, TALER_amount2s (&t->amount), t->subject.debit.exchange_base_url, "DEBIT"); @@ -265,8 +427,8 @@ check_log (struct TALER_FAKEBANK_Handle *h) case T_CREDIT: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s -> %s (%s) %s (%s)\n", - t->debit_account, - t->credit_account, + t->debit_account->account_name, + t->credit_account->account_name, TALER_amount2s (&t->amount), TALER_B2S (&t->subject.credit.reserve_pub), "CREDIT"); @@ -274,8 +436,8 @@ check_log (struct TALER_FAKEBANK_Handle *h) case T_WAD: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s -> %s (%s) %s[%s] (%s)\n", - t->debit_account, - t->credit_account, + t->debit_account->account_name, + t->credit_account->account_name, TALER_amount2s (&t->amount), t->subject.wad.origin_base_url, TALER_B2S (&t->subject.wad), @@ -294,25 +456,30 @@ TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h, const char *exchange_base_url, struct TALER_WireTransferIdentifierRawP *wtid) { - GNUNET_assert (0 == strcasecmp (want_amount->currency, - h->currency)); - for (struct Transaction *t = h->transactions_head; + struct Account *debit_account; + struct Account *credit_account; + + GNUNET_assert (0 == + strcasecmp (want_amount->currency, + h->currency)); + debit_account = lookup_account (h, + want_debit); + credit_account = lookup_account (h, + want_credit); + for (struct Transaction *t = debit_account->out_tail; NULL != t; - t = t->next) + t = t->prev_out) { - if ( (0 == strcasecmp (want_debit, - t->debit_account)) && - (0 == strcasecmp (want_credit, - t->credit_account)) && + if ( (t->unchecked) && + (credit_account == t->credit_account) && + (T_DEBIT == t->type) && (0 == TALER_amount_cmp (want_amount, &t->amount)) && - (GNUNET_NO == t->checked) && - (T_DEBIT == t->type) && (0 == strcasecmp (exchange_base_url, t->subject.debit.exchange_base_url)) ) { *wtid = t->subject.debit.wtid; - t->checked = GNUNET_YES; + t->unchecked = false; return GNUNET_OK; } } @@ -336,24 +503,28 @@ TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h, const char *want_credit, const struct TALER_ReservePublicKeyP *reserve_pub) { + struct Account *debit_account; + struct Account *credit_account; + GNUNET_assert (0 == strcasecmp (want_amount->currency, h->currency)); - for (struct Transaction *t = h->transactions_head; + debit_account = lookup_account (h, + want_debit); + credit_account = lookup_account (h, + want_credit); + for (struct Transaction *t = credit_account->in_tail; NULL != t; - t = t->next) + t = t->prev_in) { - if ( (0 == strcasecmp (want_debit, - t->debit_account)) && - (0 == strcasecmp (want_credit, - t->credit_account)) && + if ( (t->unchecked) && + (debit_account == t->debit_account) && + (T_CREDIT == t->type) && (0 == TALER_amount_cmp (want_amount, &t->amount)) && - (GNUNET_NO == t->checked) && - (T_CREDIT == t->type) && (0 == GNUNET_memcmp (reserve_pub, &t->subject.credit.reserve_pub)) ) { - t->checked = GNUNET_YES; + t->unchecked = false; return GNUNET_OK; } } @@ -370,6 +541,137 @@ TALER_FAKEBANK_check_credit (struct TALER_FAKEBANK_Handle *h, } +/** + * Clean up space used by old transaction @a t. + * The transaction @a t must already be locked + * when calling this function! + * + * @param[in,out] h bank handle + * @param[in] t transaction to clean up + */ +static void +clean_transaction (struct TALER_FAKEBANK_Handle *h, + struct Transaction *t) +{ + struct Account *da = t->debit_account; + struct Account *ca = t->credit_account; + + if (NULL == da) + return; /* nothing to be cleaned */ + + /* slot was already in use, must clean out old + entry first! */ + GNUNET_assert (0 == + pthread_mutex_lock (&da->lock)); + GNUNET_CONTAINER_MDLL_remove (out, + da->out_head, + da->out_tail, + t); + GNUNET_assert (0 == + pthread_mutex_unlock (&da->lock)); + GNUNET_assert (0 == + pthread_mutex_lock (&ca->lock)); + GNUNET_CONTAINER_MDLL_remove (in, + ca->in_head, + ca->in_tail, + t); + GNUNET_assert (0 == + pthread_mutex_unlock (&ca->lock)); + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_remove (h->uuid_map, + &t->request_uid, + t)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + t->debit_account = NULL; + t->credit_account = NULL; +} + + +/** + * Update @a account balance by @a amount. + * + * The @a account must already be locked when calling + * this function. + * + * @param[in,out] account account to update + * @param amount balance change + * @param debit true to subtract, false to add @a amount + */ +static void +update_balance (struct Account *account, + const struct TALER_Amount *amount, + bool debit) +{ + if (debit == account->is_negative) + { + GNUNET_assert (0 <= + TALER_amount_add (&account->balance, + &account->balance, + amount)); + return; + } + if (0 <= TALER_amount_cmp (&account->balance, + amount)) + { + GNUNET_assert (0 <= + TALER_amount_subtract (&account->balance, + &account->balance, + amount)); + } + else + { + GNUNET_assert (0 <= + TALER_amount_subtract (&account->balance, + amount, + &account->balance)); + account->is_negative = ! account->is_negative; + } +} + + +/** + * Add transaction to the debit and credit accounts, + * updating the balances as needed. + * + * The transaction @a t must already be locked + * when calling this function! + * + * @param[in] t transaction to clean up + */ +static void +post_transaction (struct Transaction *t) +{ + struct Account *debit_acc = t->debit_account; + struct Account *credit_acc = t->credit_account; + + GNUNET_assert (0 == + pthread_mutex_lock (&debit_acc->lock)); + GNUNET_CONTAINER_MDLL_insert_tail (out, + debit_acc->out_head, + debit_acc->out_tail, + t); + update_balance (debit_acc, + &t->amount, + true); + GNUNET_assert (0 == + pthread_mutex_unlock (&debit_acc->lock)); + GNUNET_assert (0 == + pthread_mutex_lock (&credit_acc->lock)); + GNUNET_CONTAINER_MDLL_insert_tail (in, + credit_acc->in_head, + credit_acc->in_tail, + t); + update_balance (credit_acc, + &t->amount, + false); + GNUNET_assert (0 == + pthread_mutex_unlock (&credit_acc->lock)); +} + + int TALER_FAKEBANK_make_transfer ( struct TALER_FAKEBANK_Handle *h, @@ -382,6 +684,9 @@ TALER_FAKEBANK_make_transfer ( uint64_t *ret_row_id) { struct Transaction *t; + struct Account *debit_acc; + struct Account *credit_acc; + size_t url_len; GNUNET_assert (0 == strcasecmp (amount->currency, h->currency)); @@ -391,19 +696,26 @@ TALER_FAKEBANK_make_transfer ( GNUNET_break (0 != strncasecmp ("payto://", credit_account, strlen ("payto://"))); + url_len = strlen (exchange_base_url); + GNUNET_assert (url_len < MAX_URL_LEN); + debit_acc = lookup_account (h, + debit_account); + credit_acc = lookup_account (h, + credit_account); if (NULL != request_uid) { - for (struct Transaction *t = h->transactions_head; - NULL != t; - t = t->next) + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + t = GNUNET_CONTAINER_multihashmap_get (h->uuid_map, + request_uid); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + if (NULL != t) { - if (0 != GNUNET_memcmp (request_uid, - &t->request_uid)) - continue; - if ( (0 != strcasecmp (debit_account, - t->debit_account)) || - (0 != strcasecmp (credit_account, - t->credit_account)) || + GNUNET_assert (0 == + pthread_mutex_lock (&t->lock)); + if ( (debit_acc != t->debit_account) || + (credit_acc != t->credit_account) || (0 != TALER_amount_cmp (amount, &t->amount)) || (T_DEBIT != t->type) || @@ -412,38 +724,61 @@ TALER_FAKEBANK_make_transfer ( { /* Transaction exists, but with different details. */ GNUNET_break (0); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); return GNUNET_SYSERR; } *ret_row_id = t->row_id; + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); return GNUNET_OK; } } + *ret_row_id = __sync_fetch_and_add (&h->serial_counter, + 1); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Making transfer from %s to %s over %s and subject %s; for exchange: %s\n", + "Making transfer %llu from %s to %s over %s and subject %s; for exchange: %s\n", + (unsigned long long) *ret_row_id, debit_account, credit_account, TALER_amount2s (amount), TALER_B2S (subject), exchange_base_url); - t = GNUNET_new (struct Transaction); - t->debit_account = GNUNET_strdup (debit_account); - t->credit_account = GNUNET_strdup (credit_account); + t = &h->transactions[*ret_row_id % h->ram_limit]; + GNUNET_assert (0 == + pthread_mutex_lock (&t->lock)); + clean_transaction (h, + t); + t->unchecked = true; + t->debit_account = debit_acc; + t->credit_account = credit_acc; t->amount = *amount; - t->row_id = ++h->serial_counter; + t->row_id = *ret_row_id; t->date = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&t->date); t->type = T_DEBIT; - t->subject.debit.exchange_base_url = GNUNET_strdup (exchange_base_url); + memcpy (t->subject.debit.exchange_base_url, + exchange_base_url, + url_len); t->subject.debit.wtid = *subject; if (NULL == request_uid) GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, &t->request_uid); else t->request_uid = *request_uid; - GNUNET_TIME_round_abs (&t->date); - GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head, - h->transactions_tail, - t); - *ret_row_id = t->row_id; + post_transaction (t); + GNUNET_assert (0 == + pthread_mutex_lock (&h->uuid_map_lock)); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + h->uuid_map, + &t->request_uid, + t, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->uuid_map_lock)); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); return GNUNET_OK; } @@ -458,103 +793,118 @@ TALER_FAKEBANK_make_admin_transfer ( { struct Transaction *t; const struct GNUNET_PeerIdentity *pid; + struct Account *debit_acc; + struct Account *credit_acc; + uint64_t ret; - GNUNET_assert (sizeof (*pid) == - sizeof (*reserve_pub)); + GNUNET_static_assert (sizeof (*pid) == + sizeof (*reserve_pub)); pid = (const struct GNUNET_PeerIdentity *) reserve_pub; - t = GNUNET_CONTAINER_multipeermap_get (h->rpubs, - pid); - if (NULL != t) - { - GNUNET_break (0); - return 0; - } - GNUNET_assert (0 == strcasecmp (amount->currency, - h->currency)); GNUNET_assert (NULL != debit_account); GNUNET_assert (NULL != credit_account); + GNUNET_assert (0 == strcasecmp (amount->currency, + h->currency)); GNUNET_break (0 != strncasecmp ("payto://", debit_account, strlen ("payto://"))); GNUNET_break (0 != strncasecmp ("payto://", credit_account, strlen ("payto://"))); - t = GNUNET_new (struct Transaction); - t->debit_account = GNUNET_strdup (debit_account); - t->credit_account = GNUNET_strdup (credit_account); + debit_acc = lookup_account (h, + debit_account); + credit_acc = lookup_account (h, + credit_account); + + GNUNET_assert (0 == + pthread_mutex_lock (&h->rpubs_lock)); + t = GNUNET_CONTAINER_multipeermap_get (h->rpubs, + pid); + GNUNET_assert (0 == + pthread_mutex_unlock (&h->rpubs_lock)); + if (NULL != t) + { + /* duplicate reserve public key not allowed */ + GNUNET_break (0); + return 0; + } + + ret = __sync_fetch_and_add (&h->serial_counter, + 1); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Making transfer from %s to %s over %s and subject %s at row %llu\n", + debit_account, + credit_account, + TALER_amount2s (amount), + TALER_B2S (reserve_pub), + (unsigned long long) ret); + t = &h->transactions[ret % h->ram_limit]; + GNUNET_assert (0 == + pthread_mutex_lock (&t->lock)); + clean_transaction (h, + t); + t->unchecked = true; + t->debit_account = debit_acc; + t->credit_account = credit_acc; t->amount = *amount; - t->row_id = ++h->serial_counter; + t->row_id = ret; t->date = GNUNET_TIME_absolute_get (); + GNUNET_TIME_round_abs (&t->date); t->type = T_CREDIT; t->subject.credit.reserve_pub = *reserve_pub; - GNUNET_TIME_round_abs (&t->date); - GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head, - h->transactions_tail, - t); + post_transaction (t); + GNUNET_assert (0 == + pthread_mutex_lock (&h->rpubs_lock)); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multipeermap_put ( h->rpubs, pid, t, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Making transfer from %s to %s over %s and subject %s at row %llu\n", - debit_account, - credit_account, - TALER_amount2s (amount), - TALER_B2S (reserve_pub), - (unsigned long long) t->row_id); - return t->row_id; + GNUNET_assert (0 == + pthread_mutex_unlock (&h->rpubs_lock)); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); + return ret; } int TALER_FAKEBANK_check_empty (struct TALER_FAKEBANK_Handle *h) { - struct Transaction *t; - - t = h->transactions_head; - while (NULL != t) + for (uint64_t i = 0; iram_limit; i++) { - if (GNUNET_YES != t->checked) - break; - t = t->next; + struct Transaction *t = &h->transactions[i]; + + if (t->unchecked) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected empty transaction set, but I have:\n"); + check_log (h); + return GNUNET_SYSERR; + } } - if (NULL == t) - return GNUNET_OK; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Expected empty transaction set, but I have:\n"); - check_log (h); - return GNUNET_SYSERR; + return GNUNET_OK; +} + + +static int +free_account (void *cls, + const struct GNUNET_HashCode *key, + void *val) +{ + struct Account *account = val; + + GNUNET_assert (0 == + pthread_mutex_destroy (&account->lock)); + GNUNET_free (account->account_name); + GNUNET_free (account); + return GNUNET_OK; } void TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) { - struct Transaction *t; - - while (NULL != (t = h->transactions_head)) - { - GNUNET_CONTAINER_DLL_remove (h->transactions_head, - h->transactions_tail, - t); - GNUNET_free (t->debit_account); - GNUNET_free (t->credit_account); - switch (t->type) - { - case T_CREDIT: - /* nothing to free */ - break; - case T_DEBIT: - GNUNET_free (t->subject.debit.exchange_base_url); - break; - case T_WAD: - GNUNET_free (t->subject.wad.origin_base_url); - break; - } - GNUNET_free (t); - } if (NULL != h->mhd_task) { GNUNET_SCHEDULER_cancel (h->mhd_task); @@ -568,8 +918,25 @@ TALER_FAKEBANK_stop (struct TALER_FAKEBANK_Handle *h) MHD_stop_daemon (h->mhd_bank); h->mhd_bank = NULL; } - GNUNET_free (h->my_baseurl); + if (NULL != h->accounts) + { + GNUNET_CONTAINER_multihashmap_iterate (h->accounts, + &free_account, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (h->accounts); + } + GNUNET_CONTAINER_multihashmap_destroy (h->uuid_map); GNUNET_CONTAINER_multipeermap_destroy (h->rpubs); + for (uint64_t i = 0; iram_limit; i++) + pthread_mutex_destroy (&h->transactions[i].lock); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->uuid_map_lock)); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->accounts_lock)); + GNUNET_assert (0 == + pthread_mutex_destroy (&h->rpubs_lock)); + GNUNET_free (h->transactions); + GNUNET_free (h->my_baseurl); GNUNET_free (h->currency); GNUNET_free (h); } @@ -677,6 +1044,9 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, if (0 != strcasecmp (amount.currency, h->currency)) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Currency `%s' does not match our configuration\n", + amount.currency); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_CONFLICT, @@ -698,6 +1068,8 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, GNUNET_free (debit); if (0 == row_id) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Reserve public key not unique\n"); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_CONFLICT, @@ -707,6 +1079,8 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, } json_decref (json); + // FIXME: timestamp without lock is unclean, + // return as part of TALER_FAKEBANK_make_admin_transfer instead! /* Finally build response object */ return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, @@ -715,7 +1089,7 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, (json_int_t) row_id, "timestamp", GNUNET_JSON_from_time_abs ( - h->transactions_tail->date)); + h->transactions[row_id].date)); } @@ -866,10 +1240,10 @@ handle_home_page (struct TALER_FAKEBANK_Handle *h, (void) h; (void) con_cls; - resp = MHD_create_response_from_buffer - (strlen (HELLOMSG), - HELLOMSG, - MHD_RESPMEM_MUST_COPY); + resp = MHD_create_response_from_buffer ( + strlen (HELLOMSG), + HELLOMSG, + MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response (connection, MHD_HTTP_OK, resp); @@ -907,9 +1281,9 @@ struct HistoryArgs struct GNUNET_TIME_Relative lp_timeout; /** - * #GNUNET_YES if starting point was given. + * true if starting point was given. */ - int have_start; + bool have_start; }; @@ -995,8 +1369,10 @@ handle_debit_history (struct TALER_FAKEBANK_Handle *h, const char *account) { struct HistoryArgs ha; - const struct Transaction *pos; + struct Account *acc; + struct Transaction *pos; json_t *history; + char *debit_payto; if (GNUNET_OK != parse_history_common_args (connection, @@ -1006,88 +1382,87 @@ handle_debit_history (struct TALER_FAKEBANK_Handle *h, return MHD_NO; } + acc = lookup_account (h, + account); if (! ha.have_start) { + GNUNET_assert (0 == + pthread_mutex_lock (&acc->lock)); pos = (0 > ha.delta) - ? h->transactions_tail - : h->transactions_head; + ? acc->out_tail + : acc->out_head; } - else if (NULL != h->transactions_head) + else { - for (pos = h->transactions_head; - NULL != pos; - pos = pos->next) - if (pos->row_id == ha.start_idx) - break; - if (NULL == pos) + struct Transaction *t = &h->transactions[ha.start_idx]; + + GNUNET_assert (0 == + pthread_mutex_lock (&t->lock)); + if (t->debit_account != acc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid start specified, transaction %llu not known!\n", - (unsigned long long) ha.start_idx); + "Invalid start specified, transaction %llu not with account %s!\n", + (unsigned long long) ha.start_idx, + account); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); return MHD_NO; } + GNUNET_assert (0 == + pthread_mutex_lock (&acc->lock)); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); /* range is exclusive, skip the matching entry */ if (0 > ha.delta) - pos = pos->prev; + pos = t->prev_out; else - pos = pos->next; - } - else - { - /* list is empty */ - pos = NULL; + pos = t->next_out; } + GNUNET_asprintf (&debit_payto, + "payto://x-taler-bank/localhost/%s", + account); history = json_array (); while ( (0 != ha.delta) && (NULL != pos) ) { - if ( (0 == strcasecmp (pos->debit_account, - account)) && - (T_DEBIT == pos->type) ) - { - json_t *trans; - char *credit_payto; - char *debit_payto; - - GNUNET_asprintf (&credit_payto, - "payto://x-taler-bank/localhost/%s", - pos->credit_account); - - GNUNET_asprintf (&debit_payto, - "payto://x-taler-bank/localhost/%s", - pos->debit_account); + json_t *trans; + char *credit_payto; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "made credit_payto (%s) from credit_account (%s) within fakebank\n", - credit_payto, - pos->credit_account); - - trans = json_pack - ("{s:I, s:o, s:o, s:s, s:s, s:s, s:o}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "credit_account", credit_payto, - "debit_account", debit_payto, - "exchange_base_url", - pos->subject.debit.exchange_base_url, - "wtid", GNUNET_JSON_from_data_auto ( - &pos->subject.debit.wtid)); - GNUNET_free (credit_payto); - GNUNET_free (debit_payto); - GNUNET_assert (0 == - json_array_append_new (history, - trans)); - if (ha.delta > 0) - ha.delta--; - else - ha.delta++; - } + GNUNET_assert (T_DEBIT == pos->type); + GNUNET_asprintf (&credit_payto, + "payto://x-taler-bank/localhost/%s", + pos->credit_account->account_name); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Appending credit_payto (%s) from credit_account (%s) within fakebank\n", + credit_payto, + pos->credit_account->account_name); + trans = json_pack ( + "{s:I, s:o, s:o, s:s, s:s, s:s, s:o}", + "row_id", (json_int_t) pos->row_id, + "date", GNUNET_JSON_from_time_abs (pos->date), + "amount", TALER_JSON_from_amount (&pos->amount), + "credit_account", credit_payto, + "debit_account", debit_payto, // FIXME: inefficient to return this here always! + "exchange_base_url", + pos->subject.debit.exchange_base_url, + "wtid", GNUNET_JSON_from_data_auto ( + &pos->subject.debit.wtid)); + GNUNET_free (credit_payto); + GNUNET_assert (0 == + json_array_append_new (history, + trans)); + if (ha.delta > 0) + ha.delta--; + else + ha.delta++; if (0 > ha.delta) - pos = pos->prev; + pos = pos->prev_out; if (0 < ha.delta) - pos = pos->next; + pos = pos->next_out; } + GNUNET_assert (0 == + pthread_mutex_unlock (&acc->lock)); + GNUNET_free (debit_payto); return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, "{s:o}", @@ -1110,8 +1485,10 @@ handle_credit_history (struct TALER_FAKEBANK_Handle *h, const char *account) { struct HistoryArgs ha; + struct Account *acc; const struct Transaction *pos; json_t *history; + char *credit_payto; if (GNUNET_OK != parse_history_common_args (connection, @@ -1120,116 +1497,87 @@ handle_credit_history (struct TALER_FAKEBANK_Handle *h, GNUNET_break (0); return MHD_NO; } + acc = lookup_account (h, + account); if (! ha.have_start) { + GNUNET_assert (0 == + pthread_mutex_lock (&acc->lock)); pos = (0 > ha.delta) - ? h->transactions_tail - : h->transactions_head; + ? acc->in_tail + : acc->in_head; } - else if (NULL != h->transactions_head) + else { - for (pos = h->transactions_head; - NULL != pos; - pos = pos->next) - { - if (pos->row_id == ha.start_idx) - break; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) at %llu (looking for start index %llu)\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id, - (unsigned long long) ha.start_idx); - } - if (NULL == pos) + struct Transaction *t = &h->transactions[ha.start_idx]; + + GNUNET_assert (0 == + pthread_mutex_lock (&t->lock)); + if (t->credit_account != acc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid start specified, transaction %llu not known!\n", - (unsigned long long) ha.start_idx); + "Invalid start specified, transaction %llu not with account %s!\n", + (unsigned long long) ha.start_idx, + account); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); return MHD_NO; } + GNUNET_assert (0 == + pthread_mutex_lock (&acc->lock)); + GNUNET_assert (0 == + pthread_mutex_unlock (&t->lock)); /* range is exclusive, skip the matching entry */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) (start index %llu is exclusive)\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) ha.start_idx); if (0 > ha.delta) - pos = pos->prev; + pos = t->prev_in; else - pos = pos->next; - } - else - { - /* list is empty */ - pos = NULL; + pos = t->next_in; } + GNUNET_asprintf (&credit_payto, + "payto://x-taler-bank/localhost/%s", + account); history = json_array (); while ( (0 != ha.delta) && (NULL != pos) ) { - if ( (0 == strcasecmp (pos->credit_account, - account)) && - (T_CREDIT == pos->type) ) - { - json_t *trans; - char *credit_payto; - char *debit_payto; - - GNUNET_asprintf (&credit_payto, - "payto://x-taler-bank/localhost/%s", - pos->credit_account); - - GNUNET_asprintf (&debit_payto, - "payto://x-taler-bank/localhost/%s", - pos->debit_account); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "made credit_payto (%s) from credit_account (%s) within fakebank\n", - credit_payto, - pos->credit_account); + json_t *trans; + char *debit_payto; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning transaction %s->%s (%s) at %llu\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id); - trans = json_pack - ("{s:I, s:o, s:o, s:s, s:s, s:o}", - "row_id", (json_int_t) pos->row_id, - "date", GNUNET_JSON_from_time_abs (pos->date), - "amount", TALER_JSON_from_amount (&pos->amount), - "credit_account", credit_payto, - "debit_account", debit_payto, - "reserve_pub", GNUNET_JSON_from_data_auto ( - &pos->subject.credit.reserve_pub)); - GNUNET_free (credit_payto); - GNUNET_free (debit_payto); - GNUNET_assert (0 == - json_array_append_new (history, - trans)); - if (ha.delta > 0) - ha.delta--; - else - ha.delta++; - } - else if (T_CREDIT == pos->type) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping transaction %s->%s (%s) at row %llu\n", - pos->debit_account, - pos->credit_account, - TALER_B2S (&pos->subject.credit.reserve_pub), - (unsigned long long) pos->row_id); - } + GNUNET_assert (T_CREDIT == pos->type); + GNUNET_asprintf (&debit_payto, + "payto://x-taler-bank/localhost/%s", + pos->debit_account->account_name); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning transaction %s->%s (%s) at %llu\n", + pos->debit_account->account_name, + pos->credit_account->account_name, + TALER_B2S (&pos->subject.credit.reserve_pub), + (unsigned long long) pos->row_id); + trans = json_pack ( + "{s:I, s:o, s:o, s:s, s:s, s:o}", + "row_id", (json_int_t) pos->row_id, + "date", GNUNET_JSON_from_time_abs (pos->date), + "amount", TALER_JSON_from_amount (&pos->amount), + "credit_account", credit_payto, // FIXME: inefficient to repeat this always here! + "debit_account", debit_payto, + "reserve_pub", GNUNET_JSON_from_data_auto ( + &pos->subject.credit.reserve_pub)); + GNUNET_free (debit_payto); + GNUNET_assert (0 == + json_array_append_new (history, + trans)); + if (ha.delta > 0) + ha.delta--; + else + ha.delta++; if (0 > ha.delta) - pos = pos->prev; + pos = pos->prev_in; if (0 < ha.delta) - pos = pos->next; + pos = pos->next_in; } + GNUNET_assert (0 == + pthread_mutex_unlock (&acc->lock)); + GNUNET_free (credit_payto); return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, "{s:o}", @@ -1265,51 +1613,48 @@ serve (struct TALER_FAKEBANK_Handle *h, "Fakebank, serving URL `%s' for account `%s'\n", url, account); - if ( (0 == strcmp (url, - "/")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_home_page (h, - connection, - con_cls); - if ( (0 == strcmp (url, - "/admin/add-incoming")) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - return handle_admin_add_incoming (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcmp (url, - "/transfer")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_POST)) ) - return handle_transfer (h, - connection, - account, - upload_data, - upload_data_size, - con_cls); - if ( (0 == strcmp (url, - "/history/incoming")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_credit_history (h, - connection, - account); - if ( (0 == strcmp (url, - "/history/outgoing")) && - (NULL != account) && - (0 == strcasecmp (method, - MHD_HTTP_METHOD_GET)) ) - return handle_debit_history (h, - connection, - account); - + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) + { + if ( (0 == strcmp (url, + "/history/incoming")) && + (NULL != account) ) + return handle_credit_history (h, + connection, + account); + if ( (0 == strcmp (url, + "/history/outgoing")) && + (NULL != account) ) + return handle_debit_history (h, + connection, + account); + if (0 == strcmp (url, + "/")) + return handle_home_page (h, + connection, + con_cls); + } + else if (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)) + { + if (0 == strcmp (url, + "/admin/add-incoming")) + return handle_admin_add_incoming (h, + connection, + account, + upload_data, + upload_data_size, + con_cls); + if ( (0 == strcmp (url, + "/transfer")) && + (NULL != account) ) + return handle_transfer (h, + connection, + account, + upload_data, + upload_data_size, + con_cls); + } /* Unexpected URL path, just close the connection. */ /* We're rather impolite here, but it's a testcase. */ TALER_LOG_ERROR ("Breaking URL: %s %s\n", @@ -1509,6 +1854,7 @@ TALER_FAKEBANK_start (uint16_t port, { return TALER_FAKEBANK_start2 (port, currency, + 65536, /* RAM limit */ 0, false); } @@ -1517,17 +1863,70 @@ TALER_FAKEBANK_start (uint16_t port, struct TALER_FAKEBANK_Handle * TALER_FAKEBANK_start2 (uint16_t port, const char *currency, + uint64_t ram_limit, unsigned int num_threads, bool close_connections) { struct TALER_FAKEBANK_Handle *h; + if (SIZE_MAX / sizeof (struct Transaction) < ram_limit) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "This CPU architecture does not support keeping %llu transactions in RAM\n", + (unsigned long long) ram_limit); + return NULL; + } GNUNET_assert (strlen (currency) < TALER_CURRENCY_LEN); h = GNUNET_new (struct TALER_FAKEBANK_Handle); h->port = port; h->force_close = close_connections; - h->rpubs = GNUNET_CONTAINER_multipeermap_create (128, + h->ram_limit = ram_limit; + h->serial_counter = 1; + GNUNET_assert (0 == + pthread_mutex_init (&h->accounts_lock, + NULL)); + GNUNET_assert (0 == + pthread_mutex_init (&h->rpubs_lock, + NULL)); + GNUNET_assert (0 == + pthread_mutex_init (&h->uuid_map_lock, + NULL)); + h->transactions + = GNUNET_malloc_large (sizeof (struct Transaction) + * ram_limit); + if (NULL == h->transactions) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->accounts = GNUNET_CONTAINER_multihashmap_create (128, + GNUNET_NO); + h->uuid_map = GNUNET_CONTAINER_multihashmap_create (ram_limit * 4 / 3, + GNUNET_YES); + if (NULL == h->uuid_map) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + h->rpubs = GNUNET_CONTAINER_multipeermap_create (ram_limit * 4 / 3, GNUNET_NO); + if (NULL == h->rpubs) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + TALER_FAKEBANK_stop (h); + return NULL; + } + for (uint64_t i = 0; itransactions[i].lock, + NULL)); + } h->currency = GNUNET_strdup (currency); GNUNET_asprintf (&h->my_baseurl, "http://localhost:%u/", @@ -1549,8 +1948,7 @@ TALER_FAKEBANK_start2 (uint16_t port, MHD_OPTION_END); if (NULL == h->mhd_bank) { - GNUNET_free (h->currency); - GNUNET_free (h); + TALER_FAKEBANK_stop (h); return NULL; } #if EPOLL_SUPPORT @@ -1580,8 +1978,8 @@ TALER_FAKEBANK_start2 (uint16_t port, MHD_OPTION_END); if (NULL == h->mhd_bank) { - GNUNET_free (h->currency); - GNUNET_free (h); + GNUNET_break (0); + TALER_FAKEBANK_stop (h); return NULL; } } diff --git a/src/bank-lib/taler-fakebank-run.c b/src/bank-lib/taler-fakebank-run.c index fc8fcebf7..5aa8650a0 100644 --- a/src/bank-lib/taler-fakebank-run.c +++ b/src/bank-lib/taler-fakebank-run.c @@ -59,6 +59,7 @@ run (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg) { unsigned long long port = 8082; + unsigned long long ram = 1024 * 1024 * 128; /* 128 M entries */ char *currency_string; (void) cls; @@ -81,9 +82,20 @@ run (void *cls, "Listening on default port %llu\n", port); } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "bank", + "RAM_LIMIT", + &ram)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Maximum transaction history in RAM set to default of %llu\n", + ram); + } if (NULL == TALER_FAKEBANK_start2 ((uint16_t) port, currency_string, + ram, num_threads, (0 != connection_close) )) ret = 1; diff --git a/src/include/taler_fakebank_lib.h b/src/include/taler_fakebank_lib.h index 828282c8b..60283e7f4 100644 --- a/src/include/taler_fakebank_lib.h +++ b/src/include/taler_fakebank_lib.h @@ -64,6 +64,7 @@ TALER_FAKEBANK_start (uint16_t port, * * @param port port to listen to * @param currency which currency should the bank offer + * @param ram_limit how much memory do we use at most * @param num_threads size of the thread pool, 0 to use the GNUnet scheduler * @param close_connections true to force closing a connection after each request (no HTTP keep-alive) * @return NULL on error @@ -71,6 +72,7 @@ TALER_FAKEBANK_start (uint16_t port, struct TALER_FAKEBANK_Handle * TALER_FAKEBANK_start2 (uint16_t port, const char *currency, + uint64_t ram_limit, unsigned int num_threads, bool close_connections); @@ -81,6 +83,9 @@ TALER_FAKEBANK_start2 (uint16_t port, * or #TALER_FAKEBANK_check_credit()). * If any transactions are onrecord, return #GNUNET_SYSERR. * + * Note that this function only works in + * single-threaded mode while nothing else is happening. + * * @param h bank instance * @return #GNUNET_OK on success */ @@ -139,6 +144,9 @@ TALER_FAKEBANK_make_admin_transfer ( * to the transfer identifier and remove the transaction from the * list. If the transaction was not recorded, return #GNUNET_SYSERR. * + * Note that this function only works in + * single-threaded mode while nothing else is happening. + * * @param h bank instance * @param want_amount transfer amount desired * @param want_debit account that should have been debited @@ -162,6 +170,9 @@ TALER_FAKEBANK_check_debit (struct TALER_FAKEBANK_Handle *h, * @a want_credit account with the @a subject. If so, remove the transaction * from the list. If the transaction was not recorded, return #GNUNET_SYSERR. * + * Note that this function only works in + * single-threaded mode while nothing else is happening. + * * @param h bank instance * @param want_amount transfer amount desired * @param want_debit account that should have been debited -- cgit v1.2.3