integrate purse expiration into test, bugfixes

This commit is contained in:
Christian Grothoff 2022-05-17 11:21:20 +02:00
parent 802649c270
commit d8f1f7b761
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
15 changed files with 521 additions and 26 deletions

View File

@ -1,7 +1,9 @@
usr/bin/taler-exchange-aggregator
usr/bin/taler-exchange-closer
usr/bin/taler-exchange-dbinit
usr/bin/taler-exchange-expire
usr/bin/taler-exchange-httpd
usr/bin/taler-exchange-router
usr/bin/taler-exchange-secmod-cs
usr/bin/taler-exchange-secmod-eddsa
usr/bin/taler-exchange-secmod-rsa
@ -11,7 +13,9 @@ usr/bin/taler-exchange-wire-gateway-client
usr/share/man/man1/taler-exchange-aggregator*
usr/share/man/man1/taler-exchange-closer*
usr/share/man/man1/taler-exchange-dbinit*
usr/share/man/man1/taler-exchange-expire*
usr/share/man/man1/taler-exchange-httpd*
usr/share/man/man1/taler-exchange-router*
usr/share/man/man1/taler-exchange-secmod-eddsa*
usr/share/man/man1/taler-exchange-secmod-rsa*
usr/share/man/man1/taler-exchange-secmod-cs*

View File

@ -95,6 +95,12 @@ static int global_ret;
*/
static int test_mode;
/**
* If this is a first-time run, we immediately
* try to catch up with the present.
*/
static bool jump_mode;
/**
* Select a shard to work on.
@ -188,6 +194,7 @@ static void
release_shard (struct Shard *s)
{
enum GNUNET_DB_QueryStatus qs;
unsigned long long wc = (unsigned long long) s->work_counter;
qs = db_plugin->complete_shard (
db_plugin->cls,
@ -209,10 +216,14 @@ release_shard (struct Shard *s)
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Purse expiration shard completed with %llu purses\n",
(unsigned long long) s->work_counter);
wc);
/* normal case */
break;
}
if ( (0 == wc) &&
(test_mode) &&
(! jump_mode) )
GNUNET_SCHEDULER_shutdown ();
}
@ -262,13 +273,16 @@ run_expire (void *cls)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain database connection!\n");
abort_shard (s);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (db_plugin->start (db_plugin->cls,
if (GNUNET_OK !=
db_plugin->start (db_plugin->cls,
"expire-purse"))
{
GNUNET_break (0);
global_ret = EXIT_FAILURE;
db_plugin->rollback (db_plugin->cls);
abort_shard (s);
@ -290,6 +304,7 @@ run_expire (void *cls)
case GNUNET_DB_STATUS_SOFT_ERROR:
db_plugin->rollback (db_plugin->cls);
abort_shard (s);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_shard,
NULL);
return;
@ -303,6 +318,7 @@ run_expire (void *cls)
{
release_shard (s);
}
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_shard,
NULL);
return;
@ -310,6 +326,7 @@ run_expire (void *cls)
/* commit, and go again immediately */
s->work_counter++;
(void) commit_or_warn ();
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_expire,
s);
}
@ -343,9 +360,15 @@ run_shard (void *cls)
qs = db_plugin->begin_shard (db_plugin->cls,
"expire",
shard_size,
shard_size.rel_value_us,
jump_mode
? GNUNET_TIME_absolute_subtract (
GNUNET_TIME_absolute_get (),
shard_size).
abs_value_us
: shard_size.rel_value_us,
&s->shard_start.abs_value_us,
&s->shard_end.abs_value_us);
jump_mode = false;
if (0 >= qs)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -355,6 +378,7 @@ run_shard (void *cls)
GNUNET_free (s);
delay = GNUNET_TIME_randomized_backoff (delay,
GNUNET_TIME_UNIT_SECONDS);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_delayed (delay,
&run_shard,
NULL);
@ -368,9 +392,10 @@ run_shard (void *cls)
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_TIME_absolute_is_future (s->shard_end))
if (GNUNET_TIME_absolute_is_future (s->shard_start))
{
task = GNUNET_SCHEDULER_add_at (s->shard_end,
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_at (s->shard_start,
&run_shard,
NULL);
abort_shard (s);
@ -379,12 +404,12 @@ run_shard (void *cls)
/* If this is a first-time run, we immediately
try to catch up with the present */
if (GNUNET_TIME_absolute_is_zero (s->shard_start))
s->shard_end = GNUNET_TIME_absolute_get ();
jump_mode = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting shard [%llu:%llu]!\n",
"Starting shard [%llu:%llu)!\n",
(unsigned long long) s->shard_start.abs_value_us,
(unsigned long long) s->shard_end.abs_value_us);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_expire,
s);
}

View File

@ -309,8 +309,8 @@ struct TEH_RequestHandler
* @return MHD result code
*/
MHD_RESULT
(*get)(struct TEH_RequestContext *rc,
const char *const args[]);
(*get)(struct TEH_RequestContext *rc,
const char *const args[]);
/**

View File

@ -333,6 +333,37 @@ TEH_handler_purses_get (struct TEH_RequestContext *rc,
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break; /* handled below */
}
if (GNUNET_TIME_absolute_cmp (gc->timeout,
>,
gc->purse_expiration.abs_time))
{
/* Timeout too high, need to replace event handler */
struct TALER_PurseEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (
gc->wait_for_merge
? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
: TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
.purse_pub = gc->purse_pub
};
struct GNUNET_DB_EventHandler *eh2;
gc->timeout = gc->purse_expiration.abs_time;
eh2 = TEH_plugin->event_listen (
TEH_plugin->cls,
GNUNET_TIME_absolute_get_remaining (gc->timeout),
&rep.header,
&db_event_cb,
rc);
if (NULL == eh2)
{
GNUNET_break (0);
gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
}
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
gc->eh);
gc->eh = eh2;
}
}
if (GNUNET_TIME_absolute_is_past (gc->purse_expiration.abs_time))
{

View File

@ -1193,6 +1193,14 @@ BEGIN
'(merge_pub);'
);
-- FIXME: drop index on master (crosses shards)?
-- Or use materialized index? (needed?)
EXECUTE FORMAT (
'CREATE INDEX IF NOT EXISTS ' || table_name || '_purse_expiration '
'ON ' || table_name || ' '
'(purse_expiration);'
);
END
$$;

View File

@ -2505,8 +2505,7 @@ UPDATE known_coins
THEN 1
ELSE 0
END
WHERE coin_pub=in_coin_pub
LIMIT 1; -- just to be extra safe
WHERE coin_pub=in_coin_pub;
out_conflict=FALSE;
@ -3363,7 +3362,6 @@ END $$;
CREATE OR REPLACE FUNCTION exchange_do_expire_purse(
IN in_partner_id INT8,
IN in_start_time INT8,
IN in_end_time INT8,
OUT out_found BOOLEAN)
@ -3375,25 +3373,26 @@ DECLARE
my_deposit record;
BEGIN
UPDATE purse_requests
SET refunded=TRUE,
finished=TRUE
SELECT purse_pub
INTO my_purse_pub
FROM purse_requests
WHERE (purse_expiration >= in_start_time) AND
(purse_expiration < in_end_time) AND
(NOT finished) AND
(NOT refunded)
RETURNING purse_pub
,in_reserve_quota
,flags
INTO my_purse_pub
,my_rq
,my_flags;
ORDER BY purse_expiration ASC
LIMIT 1;
out_found = FOUND;
IF NOT FOUND
THEN
RETURN;
END IF;
UPDATE purse_requests
SET refunded=TRUE,
finished=TRUE
WHERE purse_pub=my_purse_pub;
-- restore balance to each coin deposited into the purse
FOR my_deposit IN
SELECT coin_pub
@ -3402,7 +3401,7 @@ FOR my_deposit IN
FROM purse_deposits
WHERE purse_pub = my_purse_pub
LOOP
UPDATE
UPDATE known_coins SET
remaining_frac=remaining_frac+my_deposit.amount_with_fee_frac
- CASE
WHEN remaining_frac+my_deposit.amount_with_fee_frac >= 100000000
@ -3415,9 +3414,7 @@ LOOP
THEN 1
ELSE 0
END
FROM known_coins
WHERE coin_pub = my_deposit.coin_pub
LIMIT 1; -- just to be extra safe
WHERE coin_pub = my_deposit.coin_pub;
END LOOP;
END $$;

View File

@ -1211,6 +1211,30 @@ TALER_TESTING_cmd_exec_wirewatch (const char *label,
const char *config_filename);
/**
* Make a "expire" CMD.
*
* @param label command label.
* @param config_filename configuration filename.
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_expire (const char *label,
const char *config_filename);
/**
* Make a "router" CMD.
*
* @param label command label.
* @param config_filename configuration filename.
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_router (const char *label,
const char *config_filename);
/**
* Run a "taler-exchange-aggregator" CMD.
*

View File

@ -169,6 +169,11 @@ handle_purse_get_finished (void *cls,
/* Exchange does not know about transaction;
we should pass the reply to the application */
break;
case MHD_HTTP_GONE:
/* purse expired */
dr.hr.ec = TALER_JSON_get_error_code (j);
dr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
dr.hr.ec = TALER_JSON_get_error_code (j);
dr.hr.hint = TALER_JSON_get_error_hint (j);

View File

@ -61,6 +61,8 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_exec_aggregator.c \
testing_api_cmd_exec_auditor-offline.c \
testing_api_cmd_exec_closer.c \
testing_api_cmd_exec_expire.c \
testing_api_cmd_exec_router.c \
testing_api_cmd_exec_transfer.c \
testing_api_cmd_exec_wirewatch.c \
testing_api_cmd_insert_deposit.c \

View File

@ -45,6 +45,14 @@ DB = postgres
# exchange (or the twister) is actually listening.
BASE_URL = "http://localhost:8081/"
# How big is an individual shard to be processed
# by taler-exchange-expire (in time). It may take
# this much time for an expired purse to be really
# cleaned up and the coins refunded.
EXPIRE_SHARD_SIZE = 300 ms
EXPIRE_IDLE_SLEEP_INTERVAL = 1 s
[exchangedb-postgres]
CONFIG = "postgres:///talercheck"

View File

@ -45,6 +45,15 @@ DB = postgres
# exchange (or the twister) is actually listening.
BASE_URL = "http://localhost:8081/"
# How big is an individual shard to be processed
# by taler-exchange-expire (in time). It may take
# this much time for an expired purse to be really
# cleaned up and the coins refunded.
EXPIRE_SHARD_SIZE = 300 ms
EXPIRE_IDLE_SLEEP_INTERVAL = 1 s
[exchangedb-postgres]
CONFIG = "postgres:///talercheck"

View File

@ -277,6 +277,62 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
struct TALER_TESTING_Command expire[] = {
TALER_TESTING_cmd_purse_create_with_reserve (
"purse-create-with-reserve-expire",
MHD_HTTP_OK,
"{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
true /* upload contract */,
GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_SECONDS,
1), /* expiration */
"create-reserve-1"),
TALER_TESTING_cmd_purse_poll (
"pull-poll-purse-before-expire",
MHD_HTTP_GONE,
"purse-create-with-reserve-expire",
"EUR:1",
false,
GNUNET_TIME_UNIT_MINUTES),
TALER_TESTING_cmd_purse_create_with_deposit (
"purse-with-deposit-expire",
MHD_HTTP_OK,
"{\"amount\":\"EUR:1\",\"summary\":\"ice cream\"}",
true, /* upload contract */
GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_SECONDS,
1), /* expiration */
"withdraw-coin-1",
"EUR:1.01",
NULL),
TALER_TESTING_cmd_purse_poll (
"push-poll-purse-before-expire",
MHD_HTTP_GONE,
"purse-with-deposit-expire",
"EUR:1",
true,
GNUNET_TIME_UNIT_MINUTES),
TALER_TESTING_cmd_sleep ("sleep",
2 /* seconds */),
TALER_TESTING_cmd_exec_expire ("exec-expire",
config_file),
TALER_TESTING_cmd_purse_poll_finish (
"push-merge-purse-poll-finish-expire",
GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_SECONDS,
15),
"push-poll-purse-before-expire"),
TALER_TESTING_cmd_purse_poll_finish (
"pull-deposit-purse-poll-expire-finish",
GNUNET_TIME_relative_multiply (
GNUNET_TIME_UNIT_SECONDS,
15),
"pull-poll-purse-before-expire"),
// FIXME: check coin was refunded
// FIXME: check reserve purse capacity is back up!
TALER_TESTING_cmd_end ()
};
struct TALER_TESTING_Command commands[] = {
/* setup exchange */
TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
@ -313,6 +369,8 @@ run (void *cls,
push),
TALER_TESTING_cmd_batch ("pull",
pull),
TALER_TESTING_cmd_batch ("expire",
expire),
/* End the suite. */
TALER_TESTING_cmd_end ()
};

View File

@ -0,0 +1,162 @@
/*
This file is part of TALER
Copyright (C) 2022 Taler Systems SA
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, see
<http://www.gnu.org/licenses/>
*/
/**
* @file testing/testing_api_cmd_exec_expire.c
* @brief run the taler-exchange-expire command
* @author Christian Grothoff
* @author Marcello Stanisci
*/
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
#include "taler_testing_lib.h"
/**
* State for a "expire" CMD.
*/
struct ExpireState
{
/**
* Process for the expireer.
*/
struct GNUNET_OS_Process *expire_proc;
/**
* Configuration file used by the expireer.
*/
const char *config_filename;
};
/**
* Run the command; use the `taler-exchange-expire' program.
*
* @param cls closure.
* @param cmd command currently being executed.
* @param is interpreter state.
*/
static void
expire_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct ExpireState *ws = cls;
(void) cmd;
ws->expire_proc
= GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-expire",
"taler-exchange-expire",
"-L", "INFO",
"-c", ws->config_filename,
"-t", /* exit when done */
NULL);
if (NULL == ws->expire_proc)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
TALER_TESTING_wait_for_sigchld (is);
}
/**
* Free the state of a "expire" CMD, and possibly
* kills its process if it did not terminate regularly.
*
* @param cls closure.
* @param cmd the command being freed.
*/
static void
expire_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct ExpireState *ws = cls;
(void) cmd;
if (NULL != ws->expire_proc)
{
GNUNET_break (0 ==
GNUNET_OS_process_kill (ws->expire_proc,
SIGKILL));
GNUNET_OS_process_wait (ws->expire_proc);
GNUNET_OS_process_destroy (ws->expire_proc);
ws->expire_proc = NULL;
}
GNUNET_free (ws);
}
/**
* Offer "expire" CMD internal data to other commands.
*
* @param cls closure.
* @param[out] ret result.
* @param trait name of the trait.
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
static enum GNUNET_GenericReturnValue
expire_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct ExpireState *ws = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_process (&ws->expire_proc),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait (traits,
ret,
trait,
index);
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_expire (const char *label,
const char *config_filename)
{
struct ExpireState *ws;
ws = GNUNET_new (struct ExpireState);
ws->config_filename = config_filename;
{
struct TALER_TESTING_Command cmd = {
.cls = ws,
.label = label,
.run = &expire_run,
.cleanup = &expire_cleanup,
.traits = &expire_traits
};
return cmd;
}
}
/* end of testing_api_cmd_exec_expire.c */

View File

@ -0,0 +1,161 @@
/*
This file is part of TALER
Copyright (C) 2022 Taler Systems SA
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, see
<http://www.gnu.org/licenses/>
*/
/**
* @file testing/testing_api_cmd_exec_router.c
* @brief run the taler-exchange-router command
* @author Christian Grothoff
* @author Marcello Stanisci
*/
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
#include "taler_testing_lib.h"
/**
* State for a "router" CMD.
*/
struct RouterState
{
/**
* Process for the routerer.
*/
struct GNUNET_OS_Process *router_proc;
/**
* Configuration file used by the routerer.
*/
const char *config_filename;
};
/**
* Run the command; use the `taler-exchange-router' program.
*
* @param cls closure.
* @param cmd command currently being executed.
* @param is interpreter state.
*/
static void
router_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct RouterState *ws = cls;
(void) cmd;
ws->router_proc
= GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-router",
"taler-exchange-router",
"-c", ws->config_filename,
"-t", /* exit when done */
NULL);
if (NULL == ws->router_proc)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
TALER_TESTING_wait_for_sigchld (is);
}
/**
* Free the state of a "router" CMD, and possibly
* kills its process if it did not terminate regularly.
*
* @param cls closure.
* @param cmd the command being freed.
*/
static void
router_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct RouterState *ws = cls;
(void) cmd;
if (NULL != ws->router_proc)
{
GNUNET_break (0 ==
GNUNET_OS_process_kill (ws->router_proc,
SIGKILL));
GNUNET_OS_process_wait (ws->router_proc);
GNUNET_OS_process_destroy (ws->router_proc);
ws->router_proc = NULL;
}
GNUNET_free (ws);
}
/**
* Offer "router" CMD internal data to other commands.
*
* @param cls closure.
* @param[out] ret result.
* @param trait name of the trait.
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
static enum GNUNET_GenericReturnValue
router_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct RouterState *ws = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_process (&ws->router_proc),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait (traits,
ret,
trait,
index);
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_router (const char *label,
const char *config_filename)
{
struct RouterState *ws;
ws = GNUNET_new (struct RouterState);
ws->config_filename = config_filename;
{
struct TALER_TESTING_Command cmd = {
.cls = ws,
.label = label,
.run = &router_run,
.cleanup = &router_cleanup,
.traits = &router_traits
};
return cmd;
}
}
/* end of testing_api_cmd_exec_router.c */

View File

@ -261,6 +261,7 @@ TALER_TESTING_cmd_purse_poll (
ss->expected_balance = expected_balance;
ss->expected_response_code = expected_http_status;
ss->timeout = timeout;
ss->wait_for_merge = wait_for_merge;
{
struct TALER_TESTING_Command cmd = {
.cls = ss,