From 18a020dd0241fbb7deead15b96d5f5fed9f1b9b5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 May 2017 21:11:56 +0200 Subject: implement fakebank support (incl. tests) for #5005/#4964/4959 --- src/bank-lib/Makefile.am | 4 +- src/bank-lib/bank_api_admin.c | 19 ++ src/bank-lib/bank_api_history.c | 2 +- src/bank-lib/fakebank.c | 75 +++++-- src/bank-lib/test_bank_api.c | 10 +- src/bank-lib/test_bank_api_with_fakebank.c | 12 +- src/bank-lib/test_bank_interpreter.c | 346 ++++++++++++++++++++++++++++- src/bank-lib/test_bank_interpreter.h | 21 +- src/include/taler_bank_service.h | 12 +- 9 files changed, 461 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/bank-lib/Makefile.am b/src/bank-lib/Makefile.am index 48c7e9cb0..8d673f94d 100644 --- a/src/bank-lib/Makefile.am +++ b/src/bank-lib/Makefile.am @@ -57,7 +57,9 @@ check_PROGRAMS = \ test_bank_api_with_fakebank TESTS = \ - $(check_PROGRAMS) + test_bank_api_with_fakebank +# For now, test_bank_api is known NOT to work (#5005, #4964, etc.) +# $(check_PROGRAMS) test_bank_api_SOURCES = \ test_bank_interpreter.c test_bank_interpreter.h \ diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c index 9aa37c478..af23c5791 100644 --- a/src/bank-lib/bank_api_admin.c +++ b/src/bank-lib/bank_api_admin.c @@ -78,6 +78,7 @@ handle_admin_add_incoming_finished (void *cls, const json_t *json) { struct TALER_BANK_AdminAddIncomingHandle *aai = cls; + uint64_t serial_id = UINT64_MAX; aai->job = NULL; switch (response_code) @@ -85,6 +86,23 @@ handle_admin_add_incoming_finished (void *cls, case 0: break; case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("serial_id", + &serial_id), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + response_code = 0; + break; + } + } break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the bank is buggy @@ -117,6 +135,7 @@ handle_admin_add_incoming_finished (void *cls, } aai->cb (aai->cb_cls, response_code, + serial_id, json); TALER_BANK_admin_add_incoming_cancel (aai); } diff --git a/src/bank-lib/bank_api_history.c b/src/bank-lib/bank_api_history.c index a83ad8fce..ecb3561d2 100644 --- a/src/bank-lib/bank_api_history.c +++ b/src/bank-lib/bank_api_history.c @@ -94,7 +94,7 @@ parse_account_history (struct TALER_BANK_HistoryHandle *hh, GNUNET_JSON_spec_uint64 ("row_id", &serial_id), GNUNET_JSON_spec_string ("wt_subject", - &td.wire_transfer_subject), + (const char **) &td.wire_transfer_subject), GNUNET_JSON_spec_uint64 ("counterpart", &other_account), GNUNET_JSON_spec_end() diff --git a/src/bank-lib/fakebank.c b/src/bank-lib/fakebank.c index eb7656383..f8edd082d 100644 --- a/src/bank-lib/fakebank.c +++ b/src/bank-lib/fakebank.c @@ -80,7 +80,7 @@ struct Transaction /** * Number of this transaction. */ - unsigned long long serial_id; + uint64_t serial_id; }; @@ -112,7 +112,7 @@ struct TALER_FAKEBANK_Handle /** * Number of transactions. */ - unsigned long long serial_counter; + uint64_t serial_counter; }; @@ -337,12 +337,12 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, return MHD_NO; } t->exchange_base_url = GNUNET_strdup (base_url); - t->serial_id = h->serial_counter++; + t->serial_id = ++h->serial_counter; t->date = GNUNET_TIME_absolute_get (); GNUNET_TIME_round_abs (&t->date); - GNUNET_CONTAINER_DLL_insert (h->transactions_head, - h->transactions_tail, - t); + GNUNET_CONTAINER_DLL_insert_tail (h->transactions_head, + h->transactions_tail, + t); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Receiving incoming wire transfer: %llu->%llu from %s\n", @@ -350,7 +350,36 @@ handle_admin_add_incoming (struct TALER_FAKEBANK_Handle *h, (unsigned long long) t->credit_account, t->exchange_base_url); json_decref (json); - resp = MHD_create_response_from_buffer (0, "", MHD_RESPMEM_PERSISTENT); + + /* Finally build response object */ + { + void *json_str; + size_t json_len; + + json = json_pack ("{s:I}", + "serial_id", + (json_int_t) t->serial_id); + json_str = json_dumps (json, + JSON_INDENT(2)); + if (NULL == json_str) + { + GNUNET_break (0); + return MHD_NO; + } + json_len = strlen (json_str); + resp = MHD_create_response_from_buffer (json_len, + json_str, + MHD_RESPMEM_MUST_FREE); + if (NULL == resp) + { + GNUNET_break (0); + free (json_str); + return MHD_NO; + } + (void) MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json"); + } ret = MHD_queue_response (connection, MHD_HTTP_OK, resp); @@ -395,7 +424,7 @@ handle_history (struct TALER_FAKEBANK_Handle *h, "direction"); start = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, - "start"); + "start_row"); acc = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "account_number"); @@ -433,31 +462,40 @@ handle_history (struct TALER_FAKEBANK_Handle *h, } if (NULL == dir) direction = TALER_BANK_DIRECTION_BOTH; - else if (0 == strcasecmp (dir, "CREDIT")) + else if (0 == strcasecmp (dir, + "CREDIT")) direction = TALER_BANK_DIRECTION_CREDIT; else direction = TALER_BANK_DIRECTION_DEBIT; if (NULL == start) - start_number = (count > 0) ? 0 : UINT64_MAX; - if (UINT64_MAX == start_number) { - pos = h->transactions_tail; + if (count > 0) + pos = h->transactions_head; + else + pos = h->transactions_tail; } else { - unsigned long long off = 0; - + if (NULL == h->transactions_head) + { + GNUNET_break (0); + return MHD_NO; + } for (pos = h->transactions_head; - off < start_number; - off++) + pos->serial_id != start_number; + pos = pos->next) { if (NULL == pos) { GNUNET_break (0); return MHD_NO; } - pos = pos->next; } + /* range is exclusive, skip the matching entry */ + if (count > 0) + pos = pos->next; + if (count < 0) + pos = pos->prev; } history = json_array (); while ( (NULL != pos) && @@ -542,6 +580,9 @@ handle_history (struct TALER_FAKEBANK_Handle *h, free (json_str); return MHD_NO; } + (void) MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/json"); ret = MHD_queue_response (connection, MHD_HTTP_OK, resp); diff --git a/src/bank-lib/test_bank_api.c b/src/bank-lib/test_bank_api.c index 23407697e..086a0af3a 100644 --- a/src/bank-lib/test_bank_api.c +++ b/src/bank-lib/test_bank_api.c @@ -44,7 +44,7 @@ run (void *cls) .label = "history-0", .details.history.account_number = 1, .details.history.direction = TALER_BANK_DIRECTION_BOTH, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "deposit-1", @@ -64,25 +64,25 @@ run (void *cls) .label = "history-1c", .details.history.account_number = 1, .details.history.direction = TALER_BANK_DIRECTION_CREDIT, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_HISTORY, .label = "history-2d", .details.history.account_number = 2, .details.history.direction = TALER_BANK_DIRECTION_DEBIT, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_HISTORY, .label = "history-2dr", .details.history.account_number = 2, .details.history.direction = TALER_BANK_DIRECTION_DEBIT, - .details.history.start_row = UINT64_MAX, + .details.history.start_row_ref = NULL, .details.history.num_results = -5 }, { .oc = TBI_OC_HISTORY, .label = "history-2fwd", .details.history.account_number = 2, .details.history.direction = TALER_BANK_DIRECTION_DEBIT, - .details.history.start_row = 1, + .details.history.start_row_ref = "deposit-1", .details.history.num_results = 5 }, { .oc = TBI_OC_END } }; diff --git a/src/bank-lib/test_bank_api_with_fakebank.c b/src/bank-lib/test_bank_api_with_fakebank.c index 9729fb8cb..01ab30cd9 100644 --- a/src/bank-lib/test_bank_api_with_fakebank.c +++ b/src/bank-lib/test_bank_api_with_fakebank.c @@ -43,7 +43,7 @@ run (void *cls) .label = "history-0", .details.history.account_number = 1, .details.history.direction = TALER_BANK_DIRECTION_BOTH, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 1 }, /* Add EUR:5.01 to account 1 */ { .oc = TBI_OC_ADMIN_ADD_INCOMING, @@ -58,13 +58,13 @@ run (void *cls) .label = "history-1c", .details.history.account_number = 1, .details.history.direction = TALER_BANK_DIRECTION_CREDIT, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_HISTORY, .label = "history-1d", .details.history.account_number = 1, .details.history.direction = TALER_BANK_DIRECTION_DEBIT, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_ADMIN_ADD_INCOMING, .label = "debit-2", @@ -79,18 +79,18 @@ run (void *cls) .details.admin_add_incoming.credit_account_no = 2, .details.admin_add_incoming.debit_account_no = 3, .details.admin_add_incoming.exchange_base_url = "https://exchange.org/", - .details.admin_add_incoming.amount = "PUDOS:3.21" }, + .details.admin_add_incoming.amount = "PUDOS:3.22" }, { .oc = TBI_OC_HISTORY, .label = "history-2b", .details.history.account_number = 2, .details.history.direction = TALER_BANK_DIRECTION_BOTH, - .details.history.start_row = 0, + .details.history.start_row_ref = NULL, .details.history.num_results = 5 }, { .oc = TBI_OC_HISTORY, .label = "history-2bi", .details.history.account_number = 2, .details.history.direction = TALER_BANK_DIRECTION_BOTH, - .details.history.start_row = 1, + .details.history.start_row_ref = "debit-1", .details.history.num_results = 5 }, /* check transfers arrived at fakebank */ { .oc = TBI_OC_EXPECT_TRANSFER, diff --git a/src/bank-lib/test_bank_interpreter.c b/src/bank-lib/test_bank_interpreter.c index e58651b59..96036054d 100644 --- a/src/bank-lib/test_bank_interpreter.c +++ b/src/bank-lib/test_bank_interpreter.c @@ -92,6 +92,9 @@ static void fail (struct InterpreterState *is) { *is->resultp = GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Interpreter failed at command `%s'\n", + is->commands[is->ip].label); GNUNET_SCHEDULER_shutdown (); } @@ -128,6 +131,307 @@ find_command (const struct InterpreterState *is, } +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + + /** + * Wire details. + */ + struct TALER_BANK_TransferDetails details; + + /** + * Serial ID of the wire transfer. + */ + uint64_t serial_id; + + /** + * Direction of the transfer. + */ + enum TALER_BANK_Direction direction; + +}; + + +/** + * Build history of transactions matching the current + * command in @a is. + * + * @param is interpreter state + * @param[out] rh history array to initialize + * @return number of entries in @a rh + */ +static uint64_t +build_history (struct InterpreterState *is, + struct History **rh) +{ + const struct TBI_Command *cmd = &is->commands[is->ip]; + uint64_t total; + struct History *h; + const struct TBI_Command *ref; + int inc; + unsigned int start; + unsigned int end; + int ok; + + GNUNET_assert (TBI_OC_HISTORY == cmd->oc); + if (NULL != cmd->details.history.start_row_ref) + { + ref = find_command (is, + cmd->details.history.start_row_ref); + GNUNET_assert (NULL != ref); + } + else + { + ref = NULL; + } + GNUNET_assert (0 != cmd->details.history.num_results); + if (0 == is->ip) + { + *rh = NULL; + return 0; + } + if (cmd->details.history.num_results > 0) + { + inc = 1; + start = 0; + end = is->ip - 1; + } + else + { + inc = -1; + start = is->ip - 1; + end = 0; + } + + total = 0; + ok = GNUNET_NO; + if (NULL == ref) + ok = GNUNET_YES; + for (unsigned int off = start;off != end + inc; off += inc) + { + const struct TBI_Command *pos = &is->commands[off]; + + if (TBI_OC_ADMIN_ADD_INCOMING != pos->oc) + continue; + if ( (NULL != ref) && + (ref->details.admin_add_incoming.serial_id == + pos->details.admin_add_incoming.serial_id) ) + { + total = 0; + ok = GNUNET_YES; + continue; + } + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + if (total >= cmd->details.history.num_results * inc) + break; /* hit limit specified by command */ + if ( ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.credit_account_no)) || + ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_DEBIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.debit_account_no)) ) + total++; /* found matching record */ + } + GNUNET_assert (GNUNET_YES == ok); + if (0 == total) + { + *rh = NULL; + return 0; + } + GNUNET_assert (total < UINT_MAX); + h = GNUNET_new_array ((unsigned int) total, + struct History); + total = 0; + ok = GNUNET_NO; + if (NULL == ref) + ok = GNUNET_YES; + for (unsigned int off = start;off != end + inc; off += inc) + { + const struct TBI_Command *pos = &is->commands[off]; + + if (TBI_OC_ADMIN_ADD_INCOMING != pos->oc) + continue; + if ( (NULL != ref) && + (ref->details.admin_add_incoming.serial_id == + pos->details.admin_add_incoming.serial_id) ) + { + total = 0; + ok = GNUNET_YES; + continue; + } + if (GNUNET_NO == ok) + continue; /* skip until we find the marker */ + if (total >= cmd->details.history.num_results * inc) + break; /* hit limit specified by command */ + + if ( ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.credit_account_no)) && + ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_DEBIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.debit_account_no)) ) + { + GNUNET_break (0); + continue; + } + + if ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.credit_account_no)) + { + h[total].direction = TALER_BANK_DIRECTION_CREDIT; + h[total].details.account_details + = json_pack ("{s:s, s:s, s:I}", + "type", + "test", + "bank_uri", + "http://localhost:8081", + "account_number", + (json_int_t) pos->details.admin_add_incoming.debit_account_no); + GNUNET_assert (NULL != h[total].details.account_details); + } + if ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_DEBIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.debit_account_no)) + { + h[total].direction = TALER_BANK_DIRECTION_DEBIT; + h[total].details.account_details + = json_pack ("{s:s, s:s, s:I}", + "type", + "test", + "bank_uri", + "http://localhost:8081", + "account_number", + (json_int_t) pos->details.admin_add_incoming.credit_account_no); + GNUNET_assert (NULL != h[total].details.account_details); + } + if ( ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_CREDIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.credit_account_no)) || + ( (0 != (cmd->details.history.direction & TALER_BANK_DIRECTION_DEBIT)) && + (cmd->details.history.account_number == + pos->details.admin_add_incoming.debit_account_no)) ) + { + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (pos->details.admin_add_incoming.amount, + &h[total].details.amount)); + /* h[total].execution_date; // unknown here */ + h[total].serial_id + = pos->details.admin_add_incoming.serial_id; + h[total].details.wire_transfer_subject + = GNUNET_STRINGS_data_to_string_alloc (&pos->details.admin_add_incoming.wtid, + sizeof (struct TALER_WireTransferIdentifierRawP)); + total++; + } + } + *rh = h; + return total; +} + + +/** + * Free history @a h of length @a h_len. + * + * @param h history array to free + * @param h_len number of entries in @a h + */ +static void +free_history (struct History *h, + uint64_t h_len) +{ + for (uint64_t off = 0;off= total) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test says history has at most %u results, but got result #%u to check\n", + (unsigned int) total, + off); + free_history (h, + total); + return GNUNET_SYSERR; + } + if (h[off].direction != dir) + { + GNUNET_break (0); + free_history (h, + total); + return GNUNET_SYSERR; + } + + if ( (0 != strcmp (h[off].details.wire_transfer_subject, + details->wire_transfer_subject)) || + (0 != TALER_amount_cmp (&h[off].details.amount, + &details->amount)) || + (1 != json_equal (h[off].details.account_details, + details->account_details)) ) + { + GNUNET_break (0); + free_history (h, + total); + return GNUNET_SYSERR; + } + free_history (h, + total); + return GNUNET_OK; +} + + /** * Run the main interpreter loop that performs bank operations. * @@ -143,17 +447,20 @@ interpreter_run (void *cls); * @param cls closure with the interpreter state * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request * 0 if the bank's reply is bogus (fails to follow the protocol) + * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ static void add_incoming_cb (void *cls, unsigned int http_status, + uint64_t serial_id, const json_t *json) { struct InterpreterState *is = cls; struct TBI_Command *cmd = &is->commands[is->ip]; cmd->details.admin_add_incoming.aih = NULL; + cmd->details.admin_add_incoming.serial_id = serial_id; if (cmd->details.admin_add_incoming.expected_response_code != http_status) { GNUNET_break (0); @@ -203,12 +510,31 @@ history_cb (void *cls, if (MHD_HTTP_OK != http_status) { cmd->details.history.hh = NULL; + if ( (cmd->details.history.results_obtained != + compute_result_count (is)) || + (GNUNET_YES == + cmd->details.history.failed) ) + { + GNUNET_break (0); + fail (is); + return; + } is->ip++; is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is); return; } - /* FIXME: check history data is OK! (#4959) */ + if (GNUNET_OK != + check_result (is, + cmd->details.history.results_obtained, + dir, + details)) + { + GNUNET_break (0); + cmd->details.history.failed = GNUNET_YES; + return; + } + cmd->details.history.results_obtained++; } @@ -227,6 +553,7 @@ interpreter_run (void *cls) struct TALER_Amount amount; const struct GNUNET_SCHEDULER_TaskContext *tc; struct TALER_BANK_AuthenticationData auth; + uint64_t rowid; is->task = NULL; tc = GNUNET_SCHEDULER_get_task_context (); @@ -280,13 +607,27 @@ interpreter_run (void *cls) } return; case TBI_OC_HISTORY: + if (NULL != cmd->details.history.start_row_ref) + { + ref = find_command (is, + cmd->details.history.start_row_ref); + GNUNET_assert (NULL != ref); + } + else + { + ref = NULL; + } + if (NULL != ref) + rowid = ref->details.admin_add_incoming.serial_id; + else + rowid = UINT64_MAX; cmd->details.history.hh = TALER_BANK_history (is->ctx, "http://localhost:8081", &auth, cmd->details.history.account_number, cmd->details.history.direction, - cmd->details.history.start_row, + rowid, cmd->details.history.num_results, &history_cb, is); @@ -300,6 +641,7 @@ interpreter_run (void *cls) case TBI_OC_EXPECT_TRANSFER: ref = find_command (is, cmd->details.expect_transfer.cmd_ref); + GNUNET_assert (NULL != ref); GNUNET_assert (GNUNET_OK == TALER_string_to_amount (ref->details.admin_add_incoming.amount, &amount)); diff --git a/src/bank-lib/test_bank_interpreter.h b/src/bank-lib/test_bank_interpreter.h index 06b4e2d7f..d4e9c1a6c 100644 --- a/src/bank-lib/test_bank_interpreter.h +++ b/src/bank-lib/test_bank_interpreter.h @@ -125,6 +125,11 @@ struct TBI_Command */ struct TALER_BANK_AdminAddIncomingHandle *aih; + /** + * The serial ID for this record, as returned by the bank. + */ + uint64_t serial_id; + } admin_add_incoming; struct { @@ -140,10 +145,10 @@ struct TBI_Command enum TALER_BANK_Direction direction; /** - * At which offset do we start? - * Use UINT64_MAX or 0 for the extremes. + * At which serial ID do we start? References the respective @e + * admin_add_incoming command. Use NULL for the extremes. */ - uint64_t start_row; + const char *start_row_ref; /** * How many results should be returned (if available)? @@ -155,6 +160,16 @@ struct TBI_Command */ struct TALER_BANK_HistoryHandle *hh; + /** + * How many results did we actually get? + */ + uint64_t results_obtained; + + /** + * Set to #GNUNET_YES if we encountered a problem. + */ + int failed; + } history; /** diff --git a/src/include/taler_bank_service.h b/src/include/taler_bank_service.h index 75220233d..803250d56 100644 --- a/src/include/taler_bank_service.h +++ b/src/include/taler_bank_service.h @@ -98,11 +98,13 @@ struct TALER_BANK_AdminAddIncomingHandle; * @param cls closure * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request * 0 if the bank's reply is bogus (fails to follow the protocol) + * @param serial_id unique ID of the wire transfer in the bank's records; UINT64_MAX on error * @param json detailed response from the HTTPD, or NULL if reply was not in JSON */ typedef void (*TALER_BANK_AdminAddIncomingResultCallback) (void *cls, unsigned int http_status, + uint64_t serial_id, const json_t *json); @@ -188,22 +190,22 @@ struct TALER_BANK_HistoryHandle; struct TALER_BANK_TransferDetails { /** - * amount that was transferred + * Amount that was transferred */ struct TALER_Amount amount; /** - * when did the transfer happen + * Time of the the transfer */ struct GNUNET_TIME_Absolute execution_date; /** - * wire transfer subject + * Wire transfer subject */ - const char *wire_transfer_subject; + char *wire_transfer_subject; /** - * what was the other account that was involved + * The other account that was involved */ json_t *account_details; }; -- cgit v1.2.3