implement zombie check

This commit is contained in:
Christian Grothoff 2019-07-24 00:13:53 +02:00
parent e75d552227
commit 5844a20f15
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
9 changed files with 188 additions and 104 deletions

View File

@ -179,7 +179,9 @@ deposit_transaction (void *cls,
/* Start with fee for THIS transaction */
spent = deposit->amount_with_fee;
/* add cost of all previous transactions */
/* add cost of all previous transactions; skip PAYBACK as revoked
denominations are not eligible for deposit, and if we are the old coin
pub of a revoked coin (aka a zombie), then ONLY refresh is allowed. */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&deposit->coin.coin_pub,

View File

@ -289,7 +289,7 @@ payback_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
/* Calculate remaining balance. */
/* Calculate remaining balance, including paybacks already applied. */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&pc->coin->coin_pub,

View File

@ -139,6 +139,13 @@ struct RefreshMeltContext
*/
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
/**
* Set to #GNUNET_YES if this @a dki was revoked and the operation
* is thus only allowed for zombie coins where the transaction
* history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK.
*/
int zombie_required;
};
@ -168,11 +175,12 @@ refresh_check_melt (struct MHD_Connection *connection,
/* Start with cost of this melt transaction */
spent = rmc->refresh_session.amount_with_fee;
/* add historic transaction costs of this coin */
/* add historic transaction costs of this coin, including paybacks as
we might be a zombie coin */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&rmc->refresh_session.coin.coin_pub,
GNUNET_NO,
GNUNET_YES,
&tl);
if (0 > qs)
{
@ -181,6 +189,30 @@ refresh_check_melt (struct MHD_Connection *connection,
TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
return qs;
}
if (rmc->zombie_required)
{
for (struct TALER_EXCHANGEDB_TransactionList *tp = tl;
NULL != tp;
tp = tp->next)
{
if (TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK == tp->type)
{
rmc->zombie_required = GNUNET_NO; /* was satisfied! */
break;
}
}
if (rmc->zombie_required)
{
/* zombie status not satisfied */
GNUNET_break (0);
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
*mhd_ret = TEH_RESPONSE_reply_external_error (connection,
TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE,
"denomination expired");
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
&spent,
@ -505,9 +537,8 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
TEH_KS_DKU_ZOMBIE);
if (NULL != dki)
{
/* Test if zombie-condition is actually satisfied for the coin */
if (0 /* FIXME: test if zombie-satisfied */)
rmc.dki = dki;
rmc.zombie_required = GNUNET_YES;
}
}

View File

@ -322,11 +322,10 @@ postgres_create_tables (void *cls)
",h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)"
",ev_sig BYTEA NOT NULL"
",PRIMARY KEY (rc, newcoin_index)"
",UNIQUE (h_coin_ev)"
");"),
GNUNET_PQ_make_try_execute ("CREATE INDEX refresh_revealed_coins_coin_pub_index ON "
"refresh_revealed_coins (denom_pub_hash);"),
GNUNET_PQ_make_try_execute ("CREATE INDEX refresh_revealed_coins_h_coin_ev_index ON "
"refresh_revealed_coins (h_coin_ev);"),
/* Table with the transfer keys of a refresh operation; includes
the rc for which this is the link information, the
@ -1661,23 +1660,24 @@ postgres_prepare (PGconn *db_conn)
affecting old coins of refreshed coins */
GNUNET_PQ_make_prepare ("payback_by_old_coin",
"SELECT"
" pr.coin_pub"
",pr.coin_sig"
",pr.coin_blind"
",pr.amount_val"
",pr.amount_frac"
",pr.amount_curr"
",pr.timestamp"
" coin_pub"
",coin_sig"
",coin_blind"
",amount_val"
",amount_frac"
",amount_curr"
",timestamp"
",coins.denom_pub_hash"
",coins.denom_sig"
" FROM payback_refresh"
" JOIN known_coins coins"
" USING (coin_pub)"
" WHERE h_blind_ev IN"
" (SELECT rrc.h_coin_ev"
" FROM refresh_commitments"
" JOIN refresh_revealed_coins rrc"
" USING (rc)"
" JOIN payback_refresh pr"
" ON (rrc.coin_ev = pr.h_blind_ev)"
" JOIN known_coins coins"
" ON (coins.coin_pub = pr.coin_pub)"
" WHERE old_coin_pub=$1"
" WHERE old_coin_pub=$1)"
" FOR UPDATE;",
1),
/* Used in #postgres_get_reserve_history() */
@ -4821,9 +4821,6 @@ postgres_get_coin_transactions (void *cls,
/** #TALER_EXCHANGEDB_TT_REFUND */
{ "get_refunds_by_coin",
&add_coin_refund },
/** #TALER_EXCHANGEDB_TT_OLD_COIN_PAYBACK */
{ "payback_by_old_coin",
&add_old_coin_payback },
{ NULL, NULL }
};
static const struct Work work_wp[] = {

View File

@ -523,6 +523,12 @@ enum TALER_ErrorCode
*/
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS = 1308,
/**
* The denomination of the given coin has past its expiration date and it is
* also not a valid zombie (that is, was not refreshed with the fresh coin
* being subjected to payback).
*/
TALER_EC_REFRESH_MELT_COIN_EXPIRED_NO_ZOMBIE = 1309,
/**
* The provided transfer keys do not match up with the

View File

@ -159,16 +159,27 @@ run (void *cls,
"refresh-melt-1",
CONFIG_FILE),
/* Refund coin to original coin */
TALER_TESTING_cmd_payback ("payback-1",
TALER_TESTING_cmd_payback ("payback-1a",
MHD_HTTP_OK,
"refresh-reveal-1",
"EUR:5",
"refresh-reveal-1#0",
"EUR:1",
"refresh-melt-1"),
/**
* Melt original coin AGAIN
* (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
TALER_TESTING_cmd_payback ("payback-1b",
MHD_HTTP_OK,
"refresh-reveal-1#1",
"EUR:1",
"refresh-melt-1"),
TALER_TESTING_cmd_payback ("payback-1c",
MHD_HTTP_OK,
"refresh-reveal-1#2",
"EUR:1",
"refresh-melt-1"),
/* Melt original coin AGAIN (FIXME: this command
is simply WRONG as it neither matches
the EUR:3 that were paid back NOR is melt_double
precisely right here!) -- it always tries to MELT EUR:4, which is too much! */
TALER_TESTING_cmd_refresh_melt_double
("refresh-melt-2", "EUR:4",
("refresh-melt-2", "EUR:3",
"withdraw-coin-1", MHD_HTTP_OK),
/**
* Complete (successful) melt operation, and withdraw the coins
@ -190,18 +201,18 @@ run (void *cls,
TALER_TESTING_cmd_payback ("payback-2",
MHD_HTTP_OK,
"refresh-melt-2",
"EUR:5",
"EUR:1",
"refresh-melt-2"),
/* Refund original coin to reserve */
TALER_TESTING_cmd_payback ("payback-3",
MHD_HTTP_OK,
"withdraw-coin-1",
"EUR:5",
"EUR:1",
NULL),
/* Check the money is back with the reserve */
TALER_TESTING_cmd_status ("payback-reserve-status-1",
"create-reserve-1",
"EUR:4.0",
"EUR:1.0",
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};

View File

@ -107,6 +107,51 @@ struct PaybackState
};
/**
* Parser reference to a coin.
*
* @param coin_reference of format $LABEL['#' $INDEX]?
* @param cref[out] where we return a copy of $LABEL
* @param idx[out] where we set $INDEX
* @return #GNUNET_SYSERR if $INDEX is present but not numeric
*/
static int
parse_coin_reference (const char *coin_reference,
char **cref,
unsigned int *idx)
{
const char *index;
/* We allow command references of the form "$LABEL#$INDEX" or
just "$LABEL", which implies the index is 0. Figure out
which one it is. */
index = strchr (coin_reference, '#');
if (NULL == index)
{
*idx = 0;
*cref = GNUNET_strdup (coin_reference);
return GNUNET_OK;
}
*cref = GNUNET_strndup (coin_reference,
index - coin_reference);
if (1 != sscanf (index + 1,
"%u",
idx))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n",
index,
__FILE__,
__LINE__);
GNUNET_free (*cref);
*cref = NULL;
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* Check the result of the payback request: checks whether
* the HTTP response code is good, and that the coin that
@ -137,7 +182,6 @@ payback_cb (void *cls,
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
const struct TALER_TESTING_Command *reserve_cmd;
char *cref;
const char *index;
unsigned int idx;
ps->ph = NULL;
@ -155,34 +199,15 @@ payback_cb (void *cls,
return;
}
/* We allow command references of the form "$LABEL#$INDEX" or
just "$LABEL", which implies the index is 0. Figure out
which one it is. */
index = strchr (ps->coin_reference, '#');
if (NULL == index)
{
idx = 0;
cref = GNUNET_strdup (ps->coin_reference);
}
else
{
cref = GNUNET_strndup (ps->coin_reference,
index - ps->coin_reference);
if (1 != sscanf (index,
"%u",
if (GNUNET_OK !=
parse_coin_reference (ps->coin_reference,
&cref,
&idx))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Numeric index (not `%s') required after `#' in command reference of command %s in %s:%u\n",
index,
cmd->label,
__FILE__,
__LINE__);
TALER_TESTING_interpreter_fail (is);
GNUNET_free (cref);
return;
}
}
reserve_cmd = TALER_TESTING_interpreter_lookup_command
(is, cref);
GNUNET_free (cref);
@ -309,10 +334,22 @@ payback_run (void *cls,
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *coin_sig;
struct TALER_PlanchetSecretsP planchet;
char *cref;
unsigned int idx;
ps->is = is;
if (GNUNET_OK !=
parse_coin_reference (ps->coin_reference,
&cref,
&idx))
{
TALER_TESTING_interpreter_fail (is);
return;
}
coin_cmd = TALER_TESTING_interpreter_lookup_command
(is, ps->coin_reference);
(is, cref);
GNUNET_free (cref);
if (NULL == coin_cmd)
{
@ -322,7 +359,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
(coin_cmd, 0, &coin_priv))
(coin_cmd, idx, &coin_priv))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@ -330,7 +367,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_blinding_key
(coin_cmd, 0, &blinding_key))
(coin_cmd, idx, &blinding_key))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@ -340,7 +377,7 @@ payback_run (void *cls,
planchet.blinding_key = *blinding_key;
if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
(coin_cmd, 0, &denom_pub))
(coin_cmd, idx, &denom_pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@ -348,7 +385,7 @@ payback_run (void *cls,
}
if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig
(coin_cmd, 0, &coin_sig))
(coin_cmd, idx, &coin_sig))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);