WIP: policy_details handling continued

- policy details generated on deposit/batch-deposit requests
- insert or update of policy details in the DB
- accumulation of amounts of multiple deposits for the same
  policy_details
This commit is contained in:
Özgür Kesim 2022-10-14 18:56:59 +02:00
parent 956e3c3065
commit 054e157af8
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
20 changed files with 1255 additions and 787 deletions

View File

@ -115,7 +115,6 @@ static struct Table tables[] = {
{ .rt = TALER_EXCHANGEDB_RT_EXTENSIONS}, { .rt = TALER_EXCHANGEDB_RT_EXTENSIONS},
{ .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS }, { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS },
{ .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS }, { .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS },
{ .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS_FULFILMENTS },
{ .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS}, { .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS},
{ .rt = TALER_EXCHANGEDB_RT_PURSE_REFUNDS}, { .rt = TALER_EXCHANGEDB_RT_PURSE_REFUNDS},
{ .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES}, { .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES},

View File

@ -1626,8 +1626,8 @@ deposit_cb (void *cls,
&h_wire, &h_wire,
&deposit->h_contract_terms, &deposit->h_contract_terms,
&deposit->coin.h_age_commitment, &deposit->coin.h_age_commitment,
deposit->has_policy_details ? deposit->has_policy ? &deposit->h_policy : NULL,
&deposit->h_policy :NULL, &h_denom_pub, &h_denom_pub,
deposit->timestamp, deposit->timestamp,
&deposit->merchant_pub, &deposit->merchant_pub,
deposit->refund_deadline, deposit->refund_deadline,

View File

@ -1041,138 +1041,6 @@ handle_post_auditors (struct TEH_RequestContext *rc,
root); root);
} }
/**
* Handle GET "/extensions/..." requests.
*
* @param rc request context
* @param args array of additional options
* @return MHD result code
*/
static MHD_RESULT
handle_get_extensions (struct TEH_RequestContext *rc,
const char *const args[])
{
const struct TALER_Extension *ext = NULL;
if (NULL == args[0])
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/extensions/$EXTENSION");
}
ext = TALER_extensions_get_by_name (args[0]);
if (NULL == ext)
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/extensions/$EXTENSION unknown");
}
if (NULL == ext->policy_get_handler)
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_IMPLEMENTED,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"GET /extensions/$EXTENSION not supported");
return ext->policy_get_handler (
rc->connection,
&args[1]);
}
/* @brief function pointer for TALER_extension.check */
static enum GNUNET_GenericReturnValue
check_serial_ids (
struct GNUNET_HashCode serial_ids[],
size_t size)
{
for (size_t i = 0; i < size; i++)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got to check serial_id: %s\n",
GNUNET_h2s (&serial_ids[i]));
return GNUNET_OK;
}
/**
* Handle POST "/extensions/..." requests.
*
* @param rc request context
* @param root uploaded JSON data
* @param args array of additional options
* @return MHD result code
*/
static MHD_RESULT
handle_post_extensions (struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[])
{
const struct TALER_Extension *ext = NULL;
json_t *output;
if (NULL == args[0])
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/extensions/$EXTENSION");
}
ext = TALER_extensions_get_by_name (args[0]);
if (NULL == ext)
{
GNUNET_break_op (0);
return r404 (rc->connection,
"/extensions/$EXTENSION unknown");
}
if (NULL == ext->policy_post_handler)
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_IMPLEMENTED,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"POST /extensions/$EXTENSION not supported");
{
enum GNUNET_GenericReturnValue ret;
struct TALER_PolicyFulfilmentOutcome *outcome;
ret = ext->policy_post_handler (root,
&args[1],
&check_serial_ids,
&outcome,
&output);
if (GNUNET_OK != ret)
{
TALER_policy_fulfilment_outcome_free (outcome);
TALER_MHD_reply_json_steal (
rc->connection,
output,
MHD_HTTP_BAD_REQUEST);
}
/* TODO: deal with outcome */
{
for (size_t i = 0; i < outcome->len; i++)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got outcome for serial_id: %s, state: %s, amount: %s\n",
GNUNET_h2s (&outcome->positions[i].serial_id),
TALER_policy_fulfilment_state_str (
outcome->positions[i].state),
TALER_amount_to_string (&outcome->positions[i].new_amount));
}
}
}
return TALER_MHD_reply_json_steal (rc->connection,
output,
MHD_HTTP_OK);
}
/** /**
* Handle incoming HTTP request. * Handle incoming HTTP request.
* *
@ -1391,17 +1259,10 @@ handle_mhd_request (void *cls,
.nargs_is_upper_bound = true .nargs_is_upper_bound = true
}, },
/* extensions endpoints */ /* extensions endpoints */
{
.url = "extensions",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handle_get_extensions,
.nargs = 4, /* Arbitrary upper bound */
.nargs_is_upper_bound = true,
},
{ {
.url = "extensions", .url = "extensions",
.method = MHD_HTTP_METHOD_POST, .method = MHD_HTTP_METHOD_POST,
.handler.post = &handle_post_extensions, .handler.post = &TEH_extensions_post_handler,
.nargs = 4, /* Arbitrary upper bound */ .nargs = 4, /* Arbitrary upper bound */
.nargs_is_upper_bound = true, .nargs_is_upper_bound = true,
}, },

View File

@ -90,14 +90,31 @@ struct BatchDepositContext
* Additional details for policy relevant for this * Additional details for policy relevant for this
* deposit operation, possibly NULL! * deposit operation, possibly NULL!
*/ */
json_t *policy_details; json_t *policy_json;
bool has_policy_details;
/**
* Will be true if policy_json were provided
*/
bool has_policy;
/** /**
* Hash over @e policy_details, might be all zero; * Hash over @e policy_details, might be all zero;
*/ */
struct TALER_ExtensionPolicyHashP h_policy; struct TALER_ExtensionPolicyHashP h_policy;
/**
* If @e policy_json was present, the corresponding policy extension
* calculates these details. These will be persisted in the policy_details
* table.
*/
struct TALER_PolicyDetails policy_details;
/**
* When @e policy_details are persisted, this contains the id of the record
* in the policy_details table.
*/
uint64_t policy_details_serial_id;
/** /**
* Time when this request was generated. Used, for example, to * Time when this request was generated. Used, for example, to
* assess when (roughly) the income was achieved for tax purposes. * assess when (roughly) the income was achieved for tax purposes.
@ -174,7 +191,7 @@ again:
&TEH_keys_exchange_sign_, &TEH_keys_exchange_sign_,
&bdc->h_contract_terms, &bdc->h_contract_terms,
&bdc->h_wire, &bdc->h_wire,
bdc->has_policy_details ? &bdc->h_policy : NULL, bdc->has_policy ? &bdc->h_policy : NULL,
bdc->exchange_timestamp, bdc->exchange_timestamp,
bdc->wire_deadline, bdc->wire_deadline,
bdc->refund_deadline, bdc->refund_deadline,
@ -247,6 +264,21 @@ batch_deposit_transaction (void *cls,
bool balance_ok; bool balance_ok;
bool in_conflict; bool in_conflict;
/* If the deposit has a policy associated to it, persist it. This will
* insert or update the record. */
if (dc->has_policy)
{
qs = TEH_plugin->persist_policy_details (TEH_plugin->cls,
&dc->policy_details,
&dc->policy_details_serial_id,
&dc->policy_details.accumulated_total,
&dc->policy_details.fulfillment_state);
if (qs < 0)
return qs;
}
/* deposit the individutal coins */
for (unsigned int i = 0; i<dc->num_coins; i++) for (unsigned int i = 0; i<dc->num_coins; i++)
{ {
const struct TALER_EXCHANGEDB_Deposit *deposit = &dc->deposits[i]; const struct TALER_EXCHANGEDB_Deposit *deposit = &dc->deposits[i];
@ -258,13 +290,19 @@ batch_deposit_transaction (void *cls,
mhd_ret); mhd_ret);
if (qs < 0) if (qs < 0)
return qs; return qs;
qs = TEH_plugin->do_deposit (TEH_plugin->cls,
qs = TEH_plugin->do_deposit (
TEH_plugin->cls,
deposit, deposit,
known_coin_id, known_coin_id,
&dc->h_payto, &dc->h_payto,
(dc->has_policy)
? &dc->policy_details_serial_id
: NULL,
&dc->exchange_timestamp, &dc->exchange_timestamp,
&balance_ok, &balance_ok,
&in_conflict); &in_conflict);
if (qs < 0) if (qs < 0)
{ {
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -474,7 +512,7 @@ parse_coin (struct MHD_Connection *connection,
&dc->h_wire, &dc->h_wire,
&dc->h_contract_terms, &dc->h_contract_terms,
&deposit->coin.h_age_commitment, &deposit->coin.h_age_commitment,
dc->has_policy_details ? &dc->h_policy : dc->has_policy ? &dc->h_policy :
NULL, NULL,
&deposit->coin.denom_pub_hash, &deposit->coin.denom_pub_hash,
dc->timestamp, dc->timestamp,
@ -501,7 +539,7 @@ parse_coin (struct MHD_Connection *connection,
but rather insert them ONCE and then per-coin only use but rather insert them ONCE and then per-coin only use
the resulting extension UUID/serial; so the data structure the resulting extension UUID/serial; so the data structure
here should be changed once we look at extensions in earnest. */ here should be changed once we look at extensions in earnest. */
deposit->policy_details = dc->policy_details; deposit->policy_json = dc->policy_json;
deposit->timestamp = dc->timestamp; deposit->timestamp = dc->timestamp;
deposit->refund_deadline = dc->refund_deadline; deposit->refund_deadline = dc->refund_deadline;
deposit->wire_deadline = dc->wire_deadline; deposit->wire_deadline = dc->wire_deadline;
@ -518,7 +556,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
struct BatchDepositContext dc; struct BatchDepositContext dc;
json_t *coins; json_t *coins;
bool no_refund_deadline = true; bool no_refund_deadline = true;
bool no_policy_details = true; bool no_policy_json = true;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("merchant_payto_uri", GNUNET_JSON_spec_string ("merchant_payto_uri",
&dc.payto_uri), &dc.payto_uri),
@ -532,8 +570,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
&coins), &coins),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("policy", GNUNET_JSON_spec_json ("policy",
&dc.policy_details), &dc.policy_json),
&no_policy_details), &no_policy_json),
GNUNET_JSON_spec_timestamp ("timestamp", GNUNET_JSON_spec_timestamp ("timestamp",
&dc.timestamp), &dc.timestamp),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
@ -564,7 +602,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
return MHD_YES; /* failure */ return MHD_YES; /* failure */
} }
dc.has_policy_details = ! no_policy_details; dc.has_policy = ! no_policy_json;
/* validate merchant's wire details (as far as we can) */ /* validate merchant's wire details (as far as we can) */
{ {
@ -610,11 +648,26 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
TALER_merchant_wire_signature_hash (dc.payto_uri, TALER_merchant_wire_signature_hash (dc.payto_uri,
&dc.wire_salt, &dc.wire_salt,
&dc.h_wire); &dc.h_wire);
if (dc.has_policy_details)
/* handle policy, if present */
if (dc.has_policy)
{ {
TALER_deposit_policy_hash (dc.policy_details, const char *error_hint = NULL;
if (GNUNET_OK !=
TALER_extensions_create_policy_details (
dc.policy_json,
&dc.policy_details,
&error_hint))
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
error_hint);
TALER_deposit_policy_hash (dc.policy_json,
&dc.h_policy); &dc.h_policy);
} }
dc.num_coins = json_array_size (coins); dc.num_coins = json_array_size (coins);
if (0 == dc.num_coins) if (0 == dc.num_coins)
{ {
@ -651,6 +704,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO; return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
} }
/* FIXME: sum all contributions for the policy_details.accumulated_total */
} }
dc.exchange_timestamp = GNUNET_TIME_timestamp_get (); dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();

View File

@ -132,6 +132,12 @@ struct DepositContext
*/ */
uint64_t known_coin_id; uint64_t known_coin_id;
/**
* When deposit->has_policy is true, and deposit->policy_details are
* persisted, this contains the id of the record in the policy_details table.
*/
uint64_t policy_details_serial_id;
}; };
@ -165,14 +171,36 @@ deposit_transaction (void *cls,
if (qs < 0) if (qs < 0)
return qs; return qs;
/* If the deposit has a policy associated to it, persist it. This will
* insert or update the record. */
if (dc->deposit->has_policy)
{
struct TALER_Amount accumulated_total;
enum TALER_PolicyFulfillmentState fulfillment_state;
qs = TEH_plugin->persist_policy_details (TEH_plugin->cls,
&dc->deposit->policy_details,
&dc->policy_details_serial_id,
&accumulated_total,
&fulfillment_state);
qs = TEH_plugin->do_deposit (TEH_plugin->cls, /* FIXME: what to do with accumulated_total and fulfillment_state ? */
if (qs < 0)
return qs;
}
qs = TEH_plugin->do_deposit (
TEH_plugin->cls,
dc->deposit, dc->deposit,
dc->known_coin_id, dc->known_coin_id,
&dc->h_payto, &dc->h_payto,
(dc->deposit->has_policy)
? &dc->policy_details_serial_id
: NULL,
&dc->exchange_timestamp, &dc->exchange_timestamp,
&balance_ok, &balance_ok,
&in_conflict); &in_conflict);
if (qs < 0) if (qs < 0)
{ {
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -219,7 +247,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Deposit deposit; struct TALER_EXCHANGEDB_Deposit deposit;
const char *payto_uri; const char *payto_uri;
struct TALER_ExtensionPolicyHashP *ph_policy = NULL; struct TALER_ExtensionPolicyHashP *ph_policy = NULL;
bool no_policy_details; bool no_policy_json;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("merchant_payto_uri", GNUNET_JSON_spec_string ("merchant_payto_uri",
&payto_uri), &payto_uri),
@ -255,8 +283,8 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&deposit.wire_deadline), &deposit.wire_deadline),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("policy", GNUNET_JSON_spec_json ("policy",
&deposit.policy_details), &deposit.policy_json),
&no_policy_details), &no_policy_json),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
struct TALER_MerchantWireHashP h_wire; struct TALER_MerchantWireHashP h_wire;
@ -284,7 +312,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
} }
/* set the state of the policy presence in the deposit struct */ /* set the state of the policy presence in the deposit struct */
deposit.has_policy_details = ! no_policy_details; deposit.has_policy = ! no_policy_json;
/* validate merchant's wire details (as far as we can) */ /* validate merchant's wire details (as far as we can) */
{ {
@ -333,28 +361,6 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&h_wire); &h_wire);
dc.deposit = &deposit; dc.deposit = &deposit;
/* Check policy */
if (deposit.has_policy_details)
{
const char *error_hint = NULL;
enum TALER_PolicyFulfilmentState state_on_timeout;
if (GNUNET_OK !=
TALER_extensions_extract_meta_data_from_policy_details (
deposit.policy_details,
&deposit.policy_serial_id,
&deposit.policy_deadline,
&state_on_timeout,
&error_hint))
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
error_hint);
GNUNET_assert (TALER_PolicyFulfilmentStateMax >= state_on_timeout);
deposit.policy_state_on_timeout = state_on_timeout;
}
/* new deposit */ /* new deposit */
dc.exchange_timestamp = GNUNET_TIME_timestamp_get (); dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
/* check denomination exists and is valid */ /* check denomination exists and is valid */
@ -420,13 +426,6 @@ TEH_handler_deposit (struct MHD_Connection *connection,
NULL); NULL);
} }
if (deposit.has_policy_details)
{
TALER_deposit_policy_hash (deposit.policy_details,
&deposit.h_policy);
ph_policy = &deposit.h_policy;
}
deposit.deposit_fee = dk->meta.fees.deposit; deposit.deposit_fee = dk->meta.fees.deposit;
/* check coin signature */ /* check coin signature */
switch (dk->denom_pub.cipher) switch (dk->denom_pub.cipher)
@ -463,6 +462,27 @@ TEH_handler_deposit (struct MHD_Connection *connection,
NULL); NULL);
} }
/* Check policy input and create policy details */
if (deposit.has_policy)
{
const char *error_hint = NULL;
if (GNUNET_OK !=
TALER_extensions_create_policy_details (
deposit.policy_json,
&deposit.policy_details,
&error_hint))
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
error_hint);
TALER_deposit_policy_hash (deposit.policy_json,
&deposit.h_policy);
ph_policy = &deposit.h_policy;
}
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK != if (GNUNET_OK !=
TALER_wallet_deposit_verify (&deposit.amount_with_fee, TALER_wallet_deposit_verify (&deposit.amount_with_fee,

View File

@ -14,7 +14,7 @@
*/ */
/** /**
* @file taler-exchange-httpd_extensions.c * @file taler-exchange-httpd_extensions.c
* @brief Handle extensions (age-restriction, peer2peer) * @brief Handle extensions (age-restriction, policy extensions)
* @author Özgür Kesim * @author Özgür Kesim
*/ */
#include "platform.h" #include "platform.h"
@ -232,4 +232,168 @@ TEH_extensions_done ()
} }
/*
* @brief Execute database transactions for /extensions/policy_* POST requests.
*
* @param cls a `struct TALER_PolicyFulfillmentOutcome`
* @param connection MHD request context
* @param[out] mhd_ret set to MHD status on error
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
policy_fulfillment_transaction (
void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
struct TALER_PolicyFulfillmentTransactionData *fulfillment = cls;
return TEH_plugin->add_policy_fulfillment_proof (TEH_plugin->cls,
fulfillment);
}
MHD_RESULT
TEH_extensions_post_handler (
struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[])
{
const struct TALER_Extension *ext = NULL;
json_t *output;
struct TALER_PolicyDetails *policy_details = NULL;
size_t policy_details_count = 0;
if (NULL == args[0])
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"/extensions/$EXTENSION");
}
ext = TALER_extensions_get_by_name (args[0]);
if (NULL == ext)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"/extensions/$EXTENSION unknown");
}
if (NULL == ext->policy_post_handler)
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_IMPLEMENTED,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"POST /extensions/$EXTENSION not supported");
/* Extract hash_codes and retrieve related policy_details from the DB */
{
enum GNUNET_GenericReturnValue ret;
enum GNUNET_DB_QueryStatus qs;
const char *error_msg;
struct GNUNET_HashCode *hcs;
size_t len;
json_t*val;
size_t idx;
json_t *jhash_codes = json_object_get (root,
"policy_hash_codes");
if (! json_is_array (jhash_codes))
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
"policy_hash_codes are missing");
len = json_array_size (jhash_codes);
hcs = GNUNET_new_array (len,
struct GNUNET_HashCode);
policy_details = GNUNET_new_array (len,
struct TALER_PolicyDetails);
json_array_foreach (jhash_codes, idx, val)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &hcs[idx]),
GNUNET_JSON_spec_end ()
};
ret = GNUNET_JSON_parse (val,
spec,
&error_msg,
NULL);
if (GNUNET_OK != ret)
break;
qs = TEH_plugin->get_policy_details (TEH_plugin->cls,
&hcs[idx],
&policy_details[idx]);
if (qs < 0)
{
error_msg = "a policy_hash_code couldn't be found";
break;
}
}
GNUNET_free (hcs);
if (GNUNET_OK != ret)
{
GNUNET_free (policy_details);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
error_msg);
}
}
{
enum GNUNET_GenericReturnValue ret;
ret = ext->policy_post_handler (root,
&args[1],
policy_details,
policy_details_count,
&output);
if (GNUNET_OK != ret)
{
TALER_MHD_reply_json_steal (
rc->connection,
output,
MHD_HTTP_BAD_REQUEST);
}
/* execute fulfillment transaction */
{
MHD_RESULT mhd_ret;
struct TALER_PolicyFulfillmentTransactionData fulfillment = {
.proof = root,
.timestamp = GNUNET_TIME_timestamp_get (),
.details = policy_details,
.details_count = policy_details_count
};
if (GNUNET_OK !=
TEH_DB_run_transaction (rc->connection,
"execute policy fulfillment",
TEH_MT_REQUEST_POLICY_FULFILMENT,
&mhd_ret,
&policy_fulfillment_transaction,
&fulfillment))
{
json_decref (output);
return mhd_ret;
}
}
}
return TALER_MHD_reply_json_steal (rc->connection,
output,
MHD_HTTP_OK);
}
/* end of taler-exchange-httpd_extensions.c */ /* end of taler-exchange-httpd_extensions.c */

View File

@ -40,4 +40,19 @@ TEH_extensions_init (void);
void void
TEH_extensions_done (void); TEH_extensions_done (void);
/**
* Handle POST "/extensions/..." requests.
*
* @param rc request context
* @param root uploaded JSON data
* @param args array of additional options
* @return MHD result code
*/
MHD_RESULT
TEH_extensions_post_handler (
struct TEH_RequestContext *rc,
const json_t *root,
const char *const args[]);
#endif #endif

View File

@ -44,7 +44,8 @@ enum TEH_MetricTypeRequest
TEH_MT_REQUEST_IDEMPOTENT_MELT = 10, TEH_MT_REQUEST_IDEMPOTENT_MELT = 10,
TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11, TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11,
TEH_MT_REQUEST_BATCH_DEPOSIT = 12, TEH_MT_REQUEST_BATCH_DEPOSIT = 12,
TEH_MT_REQUEST_COUNT = 13 /* MUST BE LAST! */ TEH_MT_REQUEST_POLICY_FULFILMENT = 13,
TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */
}; };
/** /**

View File

@ -536,76 +536,69 @@ CREATE TABLE IF NOT EXISTS refresh_transfer_keys_default
SELECT add_constraints_to_refresh_transfer_keys_partition('default'); SELECT add_constraints_to_refresh_transfer_keys_partition('default');
-- ------------------------------ policy_fulfilments ------------------------------------- -- ------------------------------ policy_fulfillments -------------------------------------
CREATE TABLE IF NOT EXISTS policy_fulfilments CREATE TABLE IF NOT EXISTS policy_fulfillments
(fulfilment_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY (fulfillment_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY
,fulfilment_timestamp INT8 NOT NULL ,fulfillment_timestamp INT8 NOT NULL
,fulfilment_proof VARCHAR) ,fulfillment_proof VARCHAR
PARTITION BY HASH (fulfilment_id); ,h_fulfillment_proof BYTEA NOT NULL CHECK(LENGTH(h_fulfillment_proof) = 64) UNIQUE
COMMENT ON TABLE policy_fulfilments ,policy_hash_codes BYTEA NOT NULL CHECK(0 = MOD(LENGTH(policy_hash_codes), 16))
IS 'Proofs of fulfilment of policies that were set in deposits'; );
COMMENT ON COLUMN policy_fulfilments.fulfilment_timestamp COMMENT ON TABLE policy_fulfillments
IS 'Timestamp of the arrival of a proof of fulfilment'; IS 'Proofs of fulfillment of policies that were set in deposits';
COMMENT ON COLUMN policy_fulfilments.fulfilment_proof COMMENT ON COLUMN policy_fulfillments.fulfillment_timestamp
IS 'JSON object with a proof of the fulfilment of a policy. Supported details depend on the policy extensions supported by the exchange.'; IS 'Timestamp of the arrival of a proof of fulfillment';
COMMENT ON COLUMN policy_fulfillments.fulfillment_proof
CREATE TABLE IF NOT EXISTS policy_fulfilments_default IS 'JSON object with a proof of the fulfillment of a policy. Supported details depend on the policy extensions supported by the exchange.';
PARTITION OF policy_fulfilments COMMENT ON COLUMN policy_fulfillments.h_fulfillment_proof
FOR VALUES WITH (MODULUS 1, REMAINDER 0); IS 'Hash of the fulfillment_proof';
COMMENT ON COLUMN policy_fulfillments.policy_hash_codes
IS 'Concatenation of the policy_hash_code of all policy_details that are fulfilled by this proof';
-- ------------------------------ policy_details ---------------------------------------- -- ------------------------------ policy_details ----------------------------------------
CREATE TABLE IF NOT EXISTS policy_details CREATE TABLE IF NOT EXISTS policy_details
(policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY (policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
,serial_id BYTEA PRIMARY KEY CHECK(LENGTH(serial_id)=64) ,policy_hash_code BYTEA PRIMARY KEY CHECK(LENGTH(policy_hash_code)=16)
,policy_options VARCHAR ,policy_json VARCHAR
,deadline INT8 NOT NULL ,deadline INT8 NOT NULL
,timeout_fulfilment_state smallint NOT NULL CHECK(timeout_fulfilment_state in (5, 6)) ,commitment_val INT8 NOT NULL
,fulfilment_state smallint NOT NULL CHECK(fulfilment_state between 0 and 6)) ,commitment_frac INT4 NOT NULL
PARTITION BY HASH (serial_id); ,accumulated_total_val INT8 NOT NULL
,accumulated_total_frac INT4 NOT NULL
,fee_val INT8 NOT NULL
,fee_frac INT4 NOT NULL
,transferable_val INT8 NOT NULL
,transferable_frac INT8 NOT NULL
,fulfillment_state smallint NOT NULL CHECK(fulfillment_state between 0 and 5)
,fulfillment_id BIGINT NULL REFERENCES policy_fulfillments (fulfillment_id) ON DELETE CASCADE
);
COMMENT ON TABLE policy_details COMMENT ON TABLE policy_details
IS 'Policies that were provided with deposits via policy extensions.'; IS 'Policies that were provided with deposits via policy extensions.';
COMMENT ON COLUMN policy_details.serial_id COMMENT ON COLUMN policy_details.policy_hash_code
IS 'ID (GNUNET_HashCode) that identifies a policy. Will be calculated by the policy extension based on the content'; IS 'ID (GNUNET_HashCode) that identifies a policy. Will be calculated by the policy extension based on the content';
COMMENT ON COLUMN policy_details.policy_options COMMENT ON COLUMN policy_details.policy_json
IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the policy extensions supported by the exchange.'; IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the policy extensions supported by the exchange.';
COMMENT ON COLUMN policy_details.deadline COMMENT ON COLUMN policy_details.deadline
IS 'Deadline until the policy must be marked as fulfilled or unfulfilled (maybe "forever")'; IS 'Deadline until the policy must be marked as fulfilled (maybe "forever")';
COMMENT ON COLUMN policy_details.timeout_fulfilment_state COMMENT ON COLUMN policy_details.commitment_val
IS 'State that a pending policy should be put into, once the deadline is reached. Allowed values are 5 (TIMEOUT, transfer coins) or 6 (TIMEOUT, coins refreshable)'; IS 'The amount that this policy commits to. Invariant: commitment >= fee';
COMMENT ON COLUMN policy_details.fulfilment_state COMMENT ON COLUMN policy_details.accumulated_total_val
IS 'State of the fulfilment: IS 'The sum of all contributions of all deposit that reference this policy. Invariant: The fulfilment_state must be Insufficient as long as accumulated_total < commitment';
- 0 (PENDING) COMMENT ON COLUMN policy_details.fee_val
- 1 (SUCCESS, transfer coins) IS 'The fee for this policy, due when the policy is fulfilled or timed out';
- 2 (SUCCESS, coins refreshable) COMMENT ON COLUMN policy_details.transferable_val
- 3 (FAILURE, transfer coins) IS 'The amount that on fulfilment or timeout will be transfered to the payto-URI''s of the corresponding deposit''s. The policy fees must have been already deducted from it. Invariant: fee+transferable <= accumulated_total. The remaining amount (accumulated_total - fee - transferable) can be refreshed by the owner of the coins when the state is Timeout or Success.';
- 4 (FAILURE, coins refreshable) COMMENT ON COLUMN policy_details.fulfillment_state
- 5 (TIMEOUT, tranfer coins) IS 'State of the fulfillment:
- 6 (TIMEOUT, coins refrehsable)'; - 0 (Failure)
- 1 (Insufficient)
CREATE TABLE IF NOT EXISTS policy_details_default - 2 (Ready)
PARTITION OF policy_details - 4 (Success)
FOR VALUES WITH (MODULUS 1, REMAINDER 0); - 5 (Timeout)';
COMMENT ON COLUMN policy_details.fulfillment_id
-- ------------------------------ policy_details_fulfilments ----------------------------- IS 'Reference to the proof of the fulfillment of this policy, if it exists. Invariant: If not NULL, this entry''s .hash_code MUST be part of the corresponding policy_fulfillments.policy_hash_codes array.';
CREATE TABLE IF NOT EXISTS policy_details_fulfilments
(fulfilment_id BIGINT NOT NULL REFERENCES policy_fulfilments(fulfilment_id) ON DELETE CASCADE
,serial_id BYTEA NOT NULL UNIQUE REFERENCES policy_details(serial_id) ON DELETE CASCADE)
PARTITION BY HASH (serial_id); -- FIXME: choose other thing to hash here?
-- FIXME: define a primary key here?
COMMENT ON TABLE policy_details_fulfilments
IS 'Links policy_details.serial_id''s with policy_fulfilments.id''s. The same proof of fulfilment can be associated with multiple serial-id''s';
COMMENT ON COLUMN policy_details_fulfilments.fulfilment_id
IS 'ID of the proof of fulfilment';
COMMENT ON COLUMN policy_details_fulfilments.serial_id
IS 'Serial-ID of the corresponding policy_detail';
CREATE TABLE IF NOT EXISTS policy_details_fulfilments_default
PARTITION OF policy_details_fulfilments
FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-- ------------------------------ deposits ---------------------------------------- -- ------------------------------ deposits ----------------------------------------

View File

@ -929,16 +929,18 @@ irbt_cb_table_policy_details (struct PostgresClosure *pg,
{ {
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint64 (&td->serial), GNUNET_PQ_query_param_uint64 (&td->serial),
NULL == td->details.policy_details.policy_options ? ((NULL == td->details.policy_details.policy_json) ||
GNUNET_PQ_query_param_null () : (td->details.policy_details.no_policy_json))
GNUNET_PQ_query_param_string ( ? GNUNET_PQ_query_param_null ()
td->details.policy_details.policy_options), : TALER_PQ_query_param_json (td->details.policy_details.policy_json),
GNUNET_PQ_query_param_timestamp ( GNUNET_PQ_query_param_timestamp (
&td->details.policy_details.deadline), &td->details.policy_details.deadline),
GNUNET_PQ_query_param_uint16 ( GNUNET_PQ_query_param_uint16 (
&td->details.policy_details.timeout_fulfilment_state), &td->details.policy_details.fulfillment_state),
GNUNET_PQ_query_param_uint16 ( (td->details.policy_details.no_fulfillment_id)
&td->details.policy_details.fulfilment_state), ? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_uint64 (
&td->details.policy_details.fulfillment_id),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };

View File

@ -1435,33 +1435,47 @@ lrbt_cb_table_policy_details (void *cls,
unsigned int num_results) unsigned int num_results)
{ {
struct LookupRecordsByTableContext *ctx = cls; struct LookupRecordsByTableContext *ctx = cls;
struct PostgresClosure *pg = ctx->pg;
struct TALER_EXCHANGEDB_TableData td = { struct TALER_EXCHANGEDB_TableData td = {
.table = TALER_EXCHANGEDB_RT_POLICY_DETAILS .table = TALER_EXCHANGEDB_RT_POLICY_DETAILS
}; };
for (unsigned int i = 0; i<num_results; i++) for (unsigned int i = 0; i<num_results; i++)
{ {
bool no_config = false;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id", GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
&td.serial), &td.serial),
GNUNET_PQ_result_spec_auto_from_type ("serial_id", GNUNET_PQ_result_spec_auto_from_type ("hash_code",
&td.details.policy_details. &td.details.policy_details.
serial_id), hash_code),
GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("policy_options", TALER_PQ_result_spec_json ("policy_json",
&td.details.policy_details. &td.details.policy_details.
policy_options), policy_json),
&no_config), &td.details.policy_details.no_policy_json),
GNUNET_PQ_result_spec_timestamp ("deadline", GNUNET_PQ_result_spec_timestamp ("deadline",
&td.details.policy_details. &td.details.policy_details.
deadline), deadline),
GNUNET_PQ_result_spec_uint16 ("timeout_fulfilment_state", TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
&td.details.policy_details. &td.details.policy_details.
timeout_fulfilment_state), commitment),
GNUNET_PQ_result_spec_uint16 ("fulfilment_state", TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
&td.details.policy_details. &td.details.policy_details.
fulfilment_state), accumulated_total),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee",
&td.details.policy_details.
fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("transferable",
&td.details.policy_details.
transferable),
GNUNET_PQ_result_spec_uint16 ("fulfillment_state",
&td.details.policy_details.
fulfillment_state),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
&td.details.policy_details.
fulfillment_id),
&td.details.policy_details.no_fulfillment_id),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
@ -1482,14 +1496,14 @@ lrbt_cb_table_policy_details (void *cls,
/** /**
* Function called with policy_fulfilments table entries. * Function called with policy_fulfillments table entries.
* *
* @param cls closure * @param cls closure
* @param result the postgres result * @param result the postgres result
* @param num_results the number of results in @a result * @param num_results the number of results in @a result
*/ */
static void static void
lrbt_cb_table_policy_fulfilments (void *cls, lrbt_cb_table_policy_fulfillments (void *cls,
PGresult *result, PGresult *result,
unsigned int num_results) unsigned int num_results)
{ {
@ -1503,17 +1517,17 @@ lrbt_cb_table_policy_fulfilments (void *cls,
bool no_config = false; bool no_config = false;
bool no_timestamp = false; bool no_timestamp = false;
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("fulfilment_id", GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
&td.serial), &td.serial),
GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_timestamp ("fulfilment_timestamp", GNUNET_PQ_result_spec_timestamp ("fulfillment_timestamp",
&td.details.policy_fulfilments. &td.details.policy_fulfillments.
fulfilment_timestamp), fulfillment_timestamp),
&no_timestamp), &no_timestamp),
GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_string ("fulfilment_proof", GNUNET_PQ_result_spec_string ("fulfillment_proof",
&td.details.policy_fulfilments. &td.details.policy_fulfillments.
fulfilment_proof), fulfillment_proof),
&no_config), &no_config),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
@ -1534,52 +1548,6 @@ lrbt_cb_table_policy_fulfilments (void *cls,
} }
/**
* Function called with policy_details_fulfilments table entries.
*
* @param cls closure
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
lrbt_cb_table_policy_details_fulfilments (void *cls,
PGresult *result,
unsigned int num_results)
{
struct LookupRecordsByTableContext *ctx = cls;
struct TALER_EXCHANGEDB_TableData td = {
.table = TALER_EXCHANGEDB_RT_POLICY_DETAILS_FULFILMENTS
};
for (unsigned int i = 0; i<num_results; i++)
{
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("fulfilment_id",
&td.details.policy_details_fulfilments.
fulfilment_id),
GNUNET_PQ_result_spec_auto_from_type ("serial_id",
&td.details.
policy_details_fulfilments.
serial_id),
GNUNET_PQ_result_spec_end
};
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
ctx->error = true;
return;
}
ctx->cb (ctx->cb_cls,
&td);
GNUNET_PQ_cleanup_result (rs);
}
}
/** /**
* Function called with purse_requests table entries. * Function called with purse_requests table entries.
* *

View File

@ -822,8 +822,8 @@ prepare_statements (struct PostgresClosure *pg)
",out_balance_ok AS balance_ok" ",out_balance_ok AS balance_ok"
",out_conflict AS conflicted" ",out_conflict AS conflicted"
" FROM exchange_do_deposit" " FROM exchange_do_deposit"
" ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20);", " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17);",
20), 17),
/* used in postgres_do_purse_deposit() */ /* used in postgres_do_purse_deposit() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"call_purse_deposit", "call_purse_deposit",
@ -3927,34 +3927,42 @@ prepare_statements (struct PostgresClosure *pg)
"($1, $2, $3);", "($1, $2, $3);",
3), 3),
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"insert_into_table_policy_details", "call_insert_or_update_policy_details",
"INSERT INTO policy_details" "SELECT "
"(policy_details_serial_id" " out_policy_details_serial_id as policy_serial_id"
",serial_id" ",out_accumulated_total_val as accumulated_total_val"
",policy_options" ",out_accumulated_total_frac as accumulated_total_frac"
",deadline" ",out_fulfillment_state as fulfillment_state"
",timeout_fulfilment_state" " FROM insert_or_update_policy_details"
",fulfilment_state" " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);",
") VALUES " 12),
"($1, $2, $3, $4, $5, $6);",
6),
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"insert_into_table_policy_fulfilments", "insert_proof_into_policy_fulfillments",
"INSERT INTO policy_fulfilments" "INSERT INTO policy_fulfillments "
"(fulfilment_id" "(fulfillment_timestamp "
",fulfilment_timestamp" ",fulfillment_proof"
",fulfilment_proof" ",h_fulfillment_proof"
",policy_hash_codes"
") VALUES " ") VALUES "
"($1, $2, $3);", "($1, $2, $3, $4) "
3), "RETURNING fulfillment_id;",
4),
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"insert_into_table_policy_details_fulfilments", "update_policy_details",
"INSERT INTO policy_details_fulfilments" "UPDATE policy_details "
"(fulfilment_id" "SET "
",serial_id" " deadline=$2,"
") VALUES " " commitment_val=$3,"
"($1, $2);", " commitment_frac=$4,"
2), " accumulated_total_val=$5,"
" accumulated_total_frac=$6,"
" fee_val=$7,"
" fee_frac=$8,"
" transferable_val=$9,"
" transferable_frac=$10,"
" fulfillment_state=$11 "
"WHERE policy_hash_code=$1;",
11),
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"insert_into_table_purse_requests", "insert_into_table_purse_requests",
"INSERT INTO purse_requests " "INSERT INTO purse_requests "
@ -6250,7 +6258,7 @@ compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub)
* @param deposit deposit operation details * @param deposit deposit operation details
* @param known_coin_id row of the coin in the known_coins table * @param known_coin_id row of the coin in the known_coins table
* @param h_payto hash of the merchant's bank account details * @param h_payto hash of the merchant's bank account details
* @param policy_blocked true if an extension is blocking the wire transfer * @param policy_details_serial_id (pointer to) the row ID in the policy_details table, maybe NULL.
* @param[in,out] exchange_timestamp time to use for the deposit (possibly updated) * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
* @param[out] balance_ok set to true if the balance was sufficient * @param[out] balance_ok set to true if the balance was sufficient
* @param[out] in_conflict set to true if the deposit conflicted * @param[out] in_conflict set to true if the deposit conflicted
@ -6262,6 +6270,7 @@ postgres_do_deposit (
const struct TALER_EXCHANGEDB_Deposit *deposit, const struct TALER_EXCHANGEDB_Deposit *deposit,
uint64_t known_coin_id, uint64_t known_coin_id,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
uint64_t *policy_details_serial_id,
struct GNUNET_TIME_Timestamp *exchange_timestamp, struct GNUNET_TIME_Timestamp *exchange_timestamp,
bool *balance_ok, bool *balance_ok,
bool *in_conflict) bool *in_conflict)
@ -6283,19 +6292,10 @@ postgres_do_deposit (
GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub), GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
GNUNET_PQ_query_param_auto_from_type (&deposit->csig), GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
GNUNET_PQ_query_param_uint64 (&deposit_shard), GNUNET_PQ_query_param_uint64 (&deposit_shard),
GNUNET_PQ_query_param_bool (deposit->has_policy_details), GNUNET_PQ_query_param_bool (deposit->has_policy),
(deposit->has_policy_details) (NULL == policy_details_serial_id)
? TALER_PQ_query_param_json (deposit->policy_details) ? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_null (), : GNUNET_PQ_query_param_uint64 (policy_details_serial_id),
(deposit->has_policy_details)
? GNUNET_PQ_query_param_auto_from_type (&deposit->policy_serial_id)
: GNUNET_PQ_query_param_null (),
(deposit->has_policy_details)
? GNUNET_PQ_query_param_timestamp (&deposit->policy_deadline)
: GNUNET_PQ_query_param_null (),
(deposit->has_policy_details)
? GNUNET_PQ_query_param_uint16 (&deposit->policy_state_on_timeout)
: GNUNET_PQ_query_param_null (),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
@ -6315,6 +6315,101 @@ postgres_do_deposit (
} }
/* Get the details of a policy, referenced by its hash code
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param hc The hash code under which the details to a particular policy should be found
* @param[out] details The found details
* @return query execution status
* */
static enum GNUNET_DB_QueryStatus
postgres_get_policy_details (
void *cls,
const struct GNUNET_HashCode *hc,
struct TALER_PolicyDetails *details)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (hc),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_timestamp ("deadline",
&details->deadline),
TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
&details->commitment),
TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
&details->accumulated_total),
TALER_PQ_RESULT_SPEC_AMOUNT ("policy_fee",
&details->policy_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("transferable_amount",
&details->transferable_amount),
GNUNET_PQ_result_spec_auto_from_type ("state",
&details->fulfillment_state),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint64 ("policy_fulfillment_id",
&details->policy_fulfillment_id),
&details->no_policy_fulfillment_id),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"get_policy_details",
params,
rs);
}
/* Persist the details to a policy in the policy_details table. If there
* already exists a policy, update the fields accordingly.
*
* @param details The policy details that should be persisted. If an entry for
* the given details->hash_code exists, the values will be updated.
* @param[out] policy_details_serial_id The row ID of the policy details
* @param[out] accumulated_total The total amount accumulated in that policy
* @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready.
* @return query execution status
*/
static enum GNUNET_DB_QueryStatus
postgres_persist_policy_details (
void *cls,
const struct TALER_PolicyDetails *details,
uint64_t *policy_details_serial_id,
struct TALER_Amount *accumulated_total,
enum TALER_PolicyFulfillmentState *fulfillment_state)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&details->hash_code),
TALER_PQ_query_param_json (details->policy_json),
GNUNET_PQ_query_param_timestamp (&details->deadline),
TALER_PQ_query_param_amount (&details->commitment),
TALER_PQ_query_param_amount (&details->accumulated_total),
TALER_PQ_query_param_amount (&details->policy_fee),
TALER_PQ_query_param_amount (&details->transferable_amount),
GNUNET_PQ_query_param_auto_from_type (&details->fulfillment_state),
(details->no_policy_fulfillment_id)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_uint64 (&details->policy_fulfillment_id),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
policy_details_serial_id),
TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
accumulated_total),
GNUNET_PQ_result_spec_uint32 ("fulfillment_state",
fulfillment_state),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_insert_or_update_policy_details",
params,
rs);
}
/** /**
* Perform melt operation, checking for sufficient balance * Perform melt operation, checking for sufficient balance
* of the coin and possibly persisting the melt details. * of the coin and possibly persisting the melt details.
@ -6569,6 +6664,118 @@ postgres_do_recoup_refresh (
} }
/*
* Compares two indices into an array of hash codes according to
* GNUNET_CRYPTO_hash_cmp of the content at those index positions.
*
* Used in a call qsort_t in order to generate sorted policy_hash_codes.
*/
static int
hash_code_cmp (
const void *hc1,
const void *hc2,
void *arg)
{
size_t i1 = *(size_t *) hc1;
size_t i2 = *(size_t *) hc2;
const struct TALER_PolicyDetails *d = arg;
return GNUNET_CRYPTO_hash_cmp (&d[i1].hash_code,
&d[i2].hash_code);
}
/**
* Add a proof of fulfillment into the policy_fulfillments table
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param[out] proof_id set record id for the proof
* @return query execution status
*/
static enum GNUNET_DB_QueryStatus
postgres_add_policy_fulfillment_proof (
void *cls,
struct TALER_PolicyFulfillmentTransactionData *fulfillment)
{
enum GNUNET_DB_QueryStatus qs;
struct PostgresClosure *pg = cls;
size_t count = fulfillment->details_count;
struct GNUNET_HashCode hcs[count];
/* Create the sorted policy_hash_codes */
{
size_t idx[count];
for (size_t i = 0; i < count; i++)
idx[i] = i;
/* Sort the indices according to the hash codes of the corresponding
* details. */
qsort_r (idx,
count,
sizeof(size_t),
hash_code_cmp,
fulfillment->details);
/* Finally, concatenate all hash_codes in sorted order */
for (size_t i = 0; i < count; i++)
hcs[i] = fulfillment->details[idx[i]].hash_code;
}
/* Now, add the proof to the policy_fulfillments table, retrieve the
* record_id */
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_timestamp (&fulfillment->timestamp),
TALER_PQ_query_param_json (fulfillment->proof),
GNUNET_PQ_query_param_auto_from_type (&fulfillment->h_proof),
GNUNET_PQ_query_param_fixed_size (hcs,
count * sizeof(struct GNUNET_HashCode)),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
&fulfillment->fulfillment_id),
GNUNET_PQ_result_spec_end
};
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_proof_into_policy_fulfillments",
params,
rs);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
return qs;
}
/* Now, set the states of each entry corresponding to the hash_codes in
* policy_details accordingly */
for (size_t i = 0; i < count; i++)
{
struct TALER_PolicyDetails *pos = &fulfillment->details[i];
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&pos->hash_code),
GNUNET_PQ_query_param_timestamp (&pos->deadline),
TALER_PQ_query_param_amount (&pos->commitment),
TALER_PQ_query_param_amount (&pos->accumulated_total),
TALER_PQ_query_param_amount (&pos->policy_fee),
TALER_PQ_query_param_amount (&pos->transferable_amount),
GNUNET_PQ_query_param_auto_from_type (&pos->fulfillment_state),
GNUNET_PQ_query_param_end
};
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_policy_details",
params);
if (qs < 0)
return qs;
}
}
return qs;
}
/** /**
* Closure for callbacks invoked via #postgres_get_reserve_history. * Closure for callbacks invoked via #postgres_get_reserve_history.
*/ */
@ -14470,8 +14677,8 @@ postgres_lookup_records_by_table (void *cls,
rh = &lrbt_cb_table_policy_details; rh = &lrbt_cb_table_policy_details;
break; break;
case TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS: case TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS:
statement = "select_above_serial_by_table_policy_fulfilments"; statement = "select_above_serial_by_table_policy_fulfillments";
rh = &lrbt_cb_table_policy_fulfilments; rh = &lrbt_cb_table_policy_fulfillments;
break; break;
case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
statement = "select_above_serial_by_table_purse_requests"; statement = "select_above_serial_by_table_purse_requests";
@ -14655,7 +14862,7 @@ postgres_insert_records_by_table (void *cls,
case TALER_EXCHANGEDB_RT_POLICY_DETAILS: case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
rh = &irbt_cb_table_policy_details; rh = &irbt_cb_table_policy_details;
break; break;
/* TODO: policy_details_fulfilments and policy_fulfilments */ /* TODO: policy_details_fulfillments and policy_fulfillments */
case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
rh = &irbt_cb_table_purse_requests; rh = &irbt_cb_table_purse_requests;
break; break;
@ -17110,7 +17317,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->do_withdraw = &postgres_do_withdraw; plugin->do_withdraw = &postgres_do_withdraw;
plugin->do_batch_withdraw = &postgres_do_batch_withdraw; plugin->do_batch_withdraw = &postgres_do_batch_withdraw;
plugin->do_batch_withdraw_insert = &postgres_do_batch_withdraw_insert; plugin->do_batch_withdraw_insert = &postgres_do_batch_withdraw_insert;
plugin->get_policy_details = &postgres_get_policy_details;
plugin->persist_policy_details = &postgres_persist_policy_details;
plugin->do_deposit = &postgres_do_deposit; plugin->do_deposit = &postgres_do_deposit;
plugin->add_policy_fulfillment_proof = &postgres_add_policy_fulfillment_proof;
plugin->do_melt = &postgres_do_melt; plugin->do_melt = &postgres_do_melt;
plugin->do_refund = &postgres_do_refund; plugin->do_refund = &postgres_do_refund;
plugin->do_recoup = &postgres_do_recoup; plugin->do_recoup = &postgres_do_recoup;

View File

@ -511,10 +511,7 @@ CREATE OR REPLACE FUNCTION exchange_do_deposit(
IN in_coin_sig BYTEA, IN in_coin_sig BYTEA,
IN in_shard INT8, IN in_shard INT8,
IN in_policy_blocked BOOLEAN, IN in_policy_blocked BOOLEAN,
IN in_policy_details VARCHAR, IN in_policy_details_serial_id INT8,
IN in_policy_serial_id BYTEA,
IN in_policy_deadline SMALLINT,
IN in_policy_timeout_fulfilment_state SMALLINT,
OUT out_exchange_timestamp INT8, OUT out_exchange_timestamp INT8,
OUT out_balance_ok BOOLEAN, OUT out_balance_ok BOOLEAN,
OUT out_conflict BOOLEAN) OUT out_conflict BOOLEAN)
@ -522,34 +519,11 @@ LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
wtsi INT8; -- wire target serial id wtsi INT8; -- wire target serial id
DECLARE
xdi INT8; -- eXstension details serial id
BEGIN BEGIN
-- Shards: INSERT policy_details (by policy_details_serial_id) -- Shards: INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING;
-- INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING;
-- INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING; -- INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING;
-- UPDATE known_coins (by coin_pub) -- UPDATE known_coins (by coin_pub)
IF NOT NULL in_policy_details
THEN
INSERT INTO exchange.policy_details
(serial_id
,policy_options
,deadline
,timeout_fulfilment_state
,fulfilment_state)
VALUES
(in_policy_serial_id
,in_policy_details
,in_policy_deadline
,in_policy_timeout_fulfilment_state
,0) -- 0 == pending
RETURNING policy_details_serial_id INTO xdi;
ELSE
xdi=NULL;
END IF;
INSERT INTO exchange.wire_targets INSERT INTO exchange.wire_targets
(wire_target_h_payto (wire_target_h_payto
,payto_uri) ,payto_uri)
@ -602,7 +576,8 @@ INSERT INTO exchange.deposits
,in_wire_salt ,in_wire_salt
,in_h_payto ,in_h_payto
,in_policy_blocked ,in_policy_blocked
,xdi) ,in_policy_details_serial_id
)
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
IF NOT FOUND IF NOT FOUND
@ -622,6 +597,7 @@ THEN
AND wire_target_h_payto=in_h_payto AND wire_target_h_payto=in_h_payto
AND coin_pub=in_coin_pub AND coin_pub=in_coin_pub
AND coin_sig=in_coin_sig; AND coin_sig=in_coin_sig;
-- AND policy_details_serial_id=in_policy_details_serial_id; -- FIXME: is this required for idempotency?
IF NOT FOUND IF NOT FOUND
THEN THEN
@ -2211,5 +2187,123 @@ BEGIN
END $$; END $$;
CREATE OR REPLACE FUNCTION insert_or_update_policy_details(
IN in_policy_hash_code BYTEA,
IN in_policy_json VARCHAR,
IN in_deadline INT8,
IN in_commitment_val INT8,
IN in_commitment_frac INT4,
IN in_accumulated_total_val INT8,
IN in_accumulated_total_frac INT4,
IN in_fee_val INT8,
IN in_fee_frac INT4,
IN in_transferable_val INT8,
IN in_transferable_frac INT4,
IN in_fulfillment_state SMALLINT,
OUT out_policy_details_serial_id INT8,
OUT out_accumulated_total_val INT8,
OUT out_accumulated_total_frac INT4,
OUT out_fulfillment_state SMALLINT)
LANGUAGE plpgsql
AS $$
DECLARE
cur_commiment_val INT8;
cur_commiment_frac INT4;
cur_accumulated_total_val INT8;
cur_accumulated_total_frac INT4;
BEGIN
-- First, try to create a new entry.
INSERT INTO policy_details
(policy_hash_code,
policy_json,
deadline,
commitment_val,
commitment_frac,
accumulated_total_val,
accumulated_total_frac,
fee_val,
fee_frac,
transferable_val,
transferable_frac,
fulfillment_state)
VALUES (in_policy_hash_code,
in_policy_json,
in_deadline,
in_commitment_val,
in_commitment_frac,
in_accumulated_total_val,
in_accumulated_total_frac,
in_fee_val,
in_fee_frac,
in_transferable_val,
in_transferable_frac,
in_fulfillment_state)
RETURNING (policy_details_serial_id,
accumulated_total_val,
accumulated_total_frac,
fulfillment_state)
INTO (out_policy_details_serial_id,
out_accumulated_total_val,
out_accumulated_total_frac,
out_fulfillment_state)
ON CONFLICT (policy_hash_code) DO NOTHING;
-- If the insert was successful, return
-- We assume that the fullfilment_state was correct in first place.
IF FOUND THEN
RETURN;
END IF;
-- We had a conflict, grab the parts we need to update.
SELECT (policy_details_serial_id,
commitment_val,
commitment_frac,
accumulated_total_val,
accumulated_total_frac)
INTO (out_policy_details_serial_id,
cur_commitment_val,
cur_commitment_frac,
cur_accumulated_total_val,
cur_accumulated_total_frac)
FROM policy_details
WHERE policy_hash_code = in_policy_hash_code;
-- calculate the new values (overflows throws exception)
out_accumulated_total_val = cur_accumulated_total_val + in_accumulated_total_val;
out_accumulated_total_frac = cur_accumulated_total_frac + in_accumulated_total_frac;
-- normalize
out_accumulated_total_val = out_accumulated_total_val + out_accumulated_total_frac / 100000000;
out_accumulated_total_frac = out_accumulated_total_frac % 100000000;
IF (out_accumulated_total_val > (1 << 52))
THEN
RAISE EXCEPTION "accumulation overflow";
END IF;
-- Set the fulfillment_state according to the values.
-- For now, we only update the state when it was INSUFFICIENT.
-- FIXME: What to do in case of Failure or other state?
IF (out_fullfillment_state = 1) -- INSUFFICIENT
THEN
IF (out_accumulated_total_val >= cur_commitment_val OR
(out_accumulated_total_val = cur_commitment_val AND
out_accumulated_total_frac >= cur_commitment_frac))
THEN
out_fulfillment_state = 2; -- READY
END IF;
END IF;
-- Now, update the record
UPDATE exchange.policy_details
SET
accumulated_val = out_accumulated_total_val,
accumulated_frac = out_accumulated_total_frac,
fulfillment_state = out_fulfillment_state
WHERE
policy_details_serial_id = out_policy_details_serial_id;
END $$;
COMMIT; COMMIT;

View File

@ -1500,6 +1500,7 @@ run (void *cls)
&deposit, &deposit,
known_coin_id, known_coin_id,
&h_payto, &h_payto,
NULL, /* no policy_details_serial_id */
&deposit_timestamp, &deposit_timestamp,
&balance_ok, &balance_ok,
&in_conflict)); &in_conflict));

View File

@ -153,7 +153,7 @@ struct TALER_Extension TE_age_restriction = {
.manifest = &age_restriction_manifest, .manifest = &age_restriction_manifest,
/* This extension is not a policy extension */ /* This extension is not a policy extension */
.parse_policy_details = NULL, .create_policy_details = NULL,
.policy_get_handler = NULL, .policy_get_handler = NULL,
.policy_post_handler = NULL, .policy_post_handler = NULL,
}; };

View File

@ -357,31 +357,27 @@ TALER_extensions_load_manifests (
* Policy related * Policy related
*/ */
static char *fulfilment2str[] = { static char *fulfillment2str[] = {
[TALER_PolicyFulfilmentPending] = "Pending", [TALER_PolicyFulfillmentReady] = "Ready",
[TALER_PolicyFulfilmentSuccessTransfer] = "SuccessTransfer", [TALER_PolicyFulfillmentSuccess] = "Success",
[TALER_PolicyFulfilmentSuccessRefreshable] = "SuccessRefreshable", [TALER_PolicyFulfillmentFailure] = "Failure",
[TALER_PolicyFulfilmentFailureTransfer] = "FailureTransfer", [TALER_PolicyFulfillmentTimeout] = "Timeout",
[TALER_PolicyFulfilmentFailureRefreshable] = "FailureRefreshable", [TALER_PolicyFulfillmentInsufficient] = "Insufficient",
[TALER_PolicyFulfilmentTimeoutTransfer] = "TimeoutTransfer",
[TALER_PolicyFulfilmentTimeoutRefreshable] = "TimeoutRefreshable",
}; };
const char * const char *
TALER_policy_fulfilment_state_str ( TALER_policy_fulfillment_state_str (
enum TALER_PolicyFulfilmentState state) enum TALER_PolicyFulfillmentState state)
{ {
GNUNET_assert (TALER_PolicyFulfilmentStateMax >= state); GNUNET_assert (TALER_PolicyFulfillmentStateCount > state);
return fulfilment2str[state]; return fulfillment2str[state];
} }
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_extensions_extract_meta_data_from_policy_details ( TALER_extensions_create_policy_details (
const json_t *policy_details, const json_t *policy_options,
struct GNUNET_HashCode *serial, struct TALER_PolicyDetails *details,
struct GNUNET_TIME_Timestamp *deadline,
enum TALER_PolicyFulfilmentState *state_on_deadline,
const char **error_hint) const char **error_hint)
{ {
enum GNUNET_GenericReturnValue ret; enum GNUNET_GenericReturnValue ret;
@ -391,14 +387,14 @@ TALER_extensions_extract_meta_data_from_policy_details (
*error_hint = NULL; *error_hint = NULL;
if ((NULL == policy_details) || if ((NULL == policy_options) ||
(! json_is_object (policy_details))) (! json_is_object (policy_options)))
{ {
*error_hint = "invalid policy object"; *error_hint = "invalid policy object";
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
jtype = json_object_get (policy_details, "type"); jtype = json_object_get (policy_options, "type");
if (NULL == jtype) if (NULL == jtype)
{ {
*error_hint = "no type in policy object"; *error_hint = "no type in policy object";
@ -414,7 +410,7 @@ TALER_extensions_extract_meta_data_from_policy_details (
extension = TALER_extensions_get_by_name (type); extension = TALER_extensions_get_by_name (type);
if ((NULL == extension) || if ((NULL == extension) ||
(NULL == extension->parse_policy_details)) (NULL == extension->create_policy_details))
{ {
GNUNET_break (0); GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@ -423,50 +419,13 @@ TALER_extensions_extract_meta_data_from_policy_details (
return GNUNET_NO; return GNUNET_NO;
} }
*deadline = GNUNET_TIME_UNIT_FOREVER_TS; details->deadline = GNUNET_TIME_UNIT_FOREVER_TS;
ret = extension->parse_policy_details (policy_details, ret = extension->create_policy_details (policy_options,
serial, details,
deadline,
state_on_deadline,
error_hint); error_hint);
GNUNET_assert ((TALER_PolicyFulfilmentTimeoutRefreshable ==
*state_on_deadline) ||
(TALER_PolicyFulfilmentTimeoutTransfer ==
*state_on_deadline));
return ret; return ret;
} }
struct TALER_PolicyFulfilmentOutcome *
TALER_policy_fulfilment_outcome_new (size_t len)
{
struct TALER_PolicyFulfilmentOutcome *out;
out = GNUNET_new (struct TALER_PolicyFulfilmentOutcome);
out->len = len;
out->positions = GNUNET_new_array (len,
struct
TALER_PolicyFulfilmentOutcomePosition);
return out;
}
void
TALER_policy_fulfilment_outcome_free (
struct TALER_PolicyFulfilmentOutcome *outcome)
{
if (NULL == outcome)
return;
if (NULL != outcome->positions)
GNUNET_free (outcome->positions);
GNUNET_free (outcome);
}
/* end of extensions.c */ /* end of extensions.c */

View File

@ -31,7 +31,7 @@
#define MAX_RESULT_SIZE 10 * 1024 #define MAX_RESULT_SIZE 10 * 1024
/* (public) configuration of this extension */ /* (public) configuration of this extension */
/* TODO: these fields need to be set in the init handler */ /* FIXME: these fields need to be set in the init handler */
static struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig BV_config = { static struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig BV_config = {
.max_bidders = 10, .max_bidders = 10,
.max_prices = 10, .max_prices = 10,
@ -82,8 +82,8 @@ struct transcript
/* Hash of the auction */ /* Hash of the auction */
struct GNUNET_HashCode h_auction; struct GNUNET_HashCode h_auction;
/* (n-1) calculated serial_ids */ /* (n-1) calculated hash_codes */
struct GNUNET_HashCode *serial_ids; struct GNUNET_HashCode *hash_codes;
/* Type of auction, see libbrandt */ /* Type of auction, see libbrandt */
@ -146,7 +146,7 @@ json_error (json_t **output,
/* Create serial as H(bidder_pub, h_auction) */ /* Create serial as H(bidder_pub, h_auction) */
static void static void
calculate_serial ( calculate_hashcode (
struct GNUNET_CRYPTO_EddsaPublicKey *pub, struct GNUNET_CRYPTO_EddsaPublicKey *pub,
struct GNUNET_HashCode *hc, struct GNUNET_HashCode *hc,
struct GNUNET_HashCode *serial) struct GNUNET_HashCode *serial)
@ -171,7 +171,7 @@ calculate_serial (
* @param[out] jerror JSON output for errors * @param[out] jerror JSON output for errors
* @return GNUNET_OK on succes * @return GNUNET_OK on succes
* *
* TODO: * FIXME:
* - parse and verify signatures * - parse and verify signatures
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
@ -181,7 +181,7 @@ parse_transcript (const json_t *jtr,
{ {
json_t *auc; json_t *auc;
// TODO: struct GNUNET_CRYPTO_EddsaSignature sig; // FIXME: struct GNUNET_CRYPTO_EddsaSignature sig;
GNUNET_assert (jtr); GNUNET_assert (jtr);
GNUNET_assert (tr); GNUNET_assert (tr);
@ -272,7 +272,7 @@ parse_transcript (const json_t *jtr,
tr->n, tr->n,
struct GNUNET_CRYPTO_EddsaPublicKey); struct GNUNET_CRYPTO_EddsaPublicKey);
tr->serial_ids = GNUNET_new_array ( tr->hash_codes = GNUNET_new_array (
tr->n, tr->n,
struct GNUNET_HashCode); struct GNUNET_HashCode);
@ -284,7 +284,7 @@ parse_transcript (const json_t *jtr,
GNUNET_JSON_spec_end (), GNUNET_JSON_spec_end (),
}; };
/* TODO: cleanup */ /* FIXME: cleanup */
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_JSON_parse (val, GNUNET_JSON_parse (val,
spec, spec,
@ -294,9 +294,9 @@ parse_transcript (const json_t *jtr,
"bidder no %ld public key couldn't be parsed", "bidder no %ld public key couldn't be parsed",
idx + 1); idx + 1);
calculate_serial (&tr->bidder_pub[idx], calculate_hashcode (&tr->bidder_pub[idx],
&tr->h_auction, &tr->h_auction,
&tr->serial_ids[idx]); &tr->hash_codes[idx]);
} }
} }
@ -316,7 +316,7 @@ parse_transcript (const json_t *jtr,
return json_error (jerror, return json_error (jerror,
"not the right no. of messages found"); "not the right no. of messages found");
/* TODO: parse and evaluate signatures */ /* FIXME: parse and evaluate signatures */
} }
// Winners // Winners
@ -358,9 +358,9 @@ parse_transcript (const json_t *jtr,
} }
} }
// TODO: parse and evalue sig of seller // FIXME: parse and evalue sig of seller
// TODO: check for max values // FIXME: check for max values
DONE: DONE:
@ -373,16 +373,18 @@ DONE:
* *
* @param[in] root The original JSON transcript * @param[in] root The original JSON transcript
* @param[in,out] transcript The transcript object parsed so far * @param[in,out] transcript The transcript object parsed so far
* @param[out] outcome Outcome object to fill * @param[in/out] details Array of policy details, provided by the exchange
* @param[in] details_count number of elements in @e details
* @param[out] result The JSON result from the program * @param[out] result The JSON result from the program
* @return GNUNET_OK on success * @return GNUNET_OK on success
* *
* TODO: Make this resumable * FIXME: Make this resumable
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
replay_transcript (const json_t*root, replay_transcript (const json_t*root,
struct transcript *tr, struct transcript *tr,
struct TALER_PolicyFulfilmentOutcome **outcome, struct TALER_PolicyDetails *details,
size_t details_count,
json_t **result) json_t **result)
{ {
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
@ -488,30 +490,33 @@ replay_transcript (const json_t*root,
return json_error (result, "internal error"); return json_error (result, "internal error");
} }
// TODO: check each winner with tr->expected, if applicable // FIXME: check each winner with tr->expected, if applicable
// Create the outcome object /* First, set all details to default state and values */
for (size_t i = 0; i< tr->n; i++)
{
/* no fees for non-winners */
TALER_amount_set_zero (details[i].policy_fee.currency,
&details[i].policy_fee);
/* no transferable amounts (=> accumulated_total becomes refreshable) */
TALER_amount_set_zero (details[i].transferable_amount.currency,
&details[i].transferable_amount);
details[i].fulfillment_state = TALER_PolicyFulfillmentSuccess;
}
/* Now, fill the winner details */
{ {
json_t *w; json_t *w;
size_t idx; size_t idx;
struct TALER_PolicyFulfilmentOutcomePosition *pos;
*outcome =
TALER_policy_fulfilment_outcome_new (tr->n);
/* Set outcome for all bidders to a default value first */
for (uint16_t i = 0; i<tr->n; i++)
{
pos = &((*outcome)->positions[i]);
pos->serial_id = tr->serial_ids[i];
pos->has_new_amount = false;
pos->state = TALER_PolicyFulfilmentFailureRefreshable;
}
/* Set the outcome of the winners */ /* Set the outcome of the winners */
json_array_foreach (winners, idx, w) json_array_foreach (winners, idx, w)
{ {
// TODO
enum GNUNET_GenericReturnValue ret; enum GNUNET_GenericReturnValue ret;
const char *jerror; const char *jerror;
uint16_t bidder; uint16_t bidder;
@ -540,7 +545,7 @@ replay_transcript (const json_t*root,
LOG_PREFIX LOG_PREFIX
"couldn't parse output of replay program: %s\n", "couldn't parse output of replay program: %s\n",
jerror); jerror);
ret = json_error (result, "internal error"); ret = json_error (result, "internal error (replay)");
goto DONE; goto DONE;
} }
@ -549,19 +554,41 @@ replay_transcript (const json_t*root,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
LOG_PREFIX LOG_PREFIX
"replay program sent a bidder out of range\n"); "replay program sent a bidder out of range\n");
ret = json_error (result, "internal error"); ret = json_error (result, "internal error (bidder)");
goto DONE; goto DONE;
} }
// Fill the outcome position for this winning bidder. // Fill the details for this winning bidder.
pos = &((*outcome)->positions[idx]); {
pos->has_new_amount = true; struct TALER_PolicyDetails *det = NULL;
pos->new_amount = price; struct GNUNET_HashCode *hc = &tr->hash_codes[idx];
pos->state = TALER_PolicyFulfilmentSuccessTransfer;
/* Find the corresponding details */
for (size_t i = 0; i < tr->n; i++)
{
if (GNUNET_CRYPTO_hash_cmp (hc,
&details[idx].hash_code))
{
det = &details[idx];
break;
}
} }
// TODO: return own result object. if (NULL == det)
// TODO: sign the result by the exchange {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
LOG_PREFIX
"Couldeln't find calculate hash code %s in details\n",
GNUNET_h2s (hc));
ret = json_error (result, "internal error (details)");
goto DONE;
}
}
}
// FIXME: return own result object.
// FIXME: sign the result by the exchange
*result = json_copy (res); *result = json_copy (res);
json_decref (res); json_decref (res);
} }
@ -582,11 +609,6 @@ replay_transcript (const json_t*root,
} }
DONE: DONE:
if (GNUNET_OK != ret)
{
TALER_policy_fulfilment_outcome_free (*outcome);
*outcome = NULL;
}
return ret; return ret;
} }
@ -642,7 +664,7 @@ auction_load_config (
struct TALER_Extension *ext, struct TALER_Extension *ext,
json_t *jconfig) json_t *jconfig)
{ {
/* TODO: parse configuration */ /* FIXME: parse configuration */
ext->enabled = true; ext->enabled = true;
return GNUNET_OK; return GNUNET_OK;
} }
@ -656,7 +678,7 @@ auction_policy_get_handler (
struct MHD_Connection *connection, struct MHD_Connection *connection,
const char *const args[]) const char *const args[])
{ {
/* TODO: return some meta-data about supported version, limits, etc.*/ /* FIXME: return some meta-data about supported version, limits, etc.*/
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
LOG_PREFIX "auction_policy_get_handler not implemented yet\n"); LOG_PREFIX "auction_policy_get_handler not implemented yet\n");
@ -672,55 +694,89 @@ auction_policy_get_handler (
/** /**
* @brief implements the TALER_Extension.policy_post_handler * @brief implements the TALER_Extension.policy_post_handler
* *
* TODO: make this non-blocking * FIXME: make this non-blocking
*/ */
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
auction_policy_post_handler ( auction_policy_post_handler (
const json_t *root, const json_t *root,
const char *const args[], const char *const args[],
enum GNUNET_GenericReturnValue (*serial_id_check)(struct GNUNET_HashCode struct TALER_PolicyDetails *details,
serial_ids[], size_t size), size_t details_len,
struct TALER_PolicyFulfilmentOutcome **outcome,
json_t **output) json_t **output)
{ {
struct transcript tr = {}; struct transcript tr = {0};
enum GNUNET_GenericReturnValue ret; enum GNUNET_GenericReturnValue ret;
*outcome = NULL; do {
ret = parse_transcript (root, ret = parse_transcript (root,
&tr, &tr,
output); output);
/* TODO: cleanups! */
if (GNUNET_OK != ret) if (GNUNET_OK != ret)
return ret; break;
serial_id_check (tr.serial_ids, tr.n); /* Compare the calculated hash_codes of policies with the provided onces */
{
if (details_len != tr.n)
{
ret = json_error (output, "wrong number of bidders");
break;
}
return replay_transcript (root, for (size_t i = 0; i<details_len; i++)
{
bool found = false;
for (size_t j = 0; j<tr.n; j++)
{
if (GNUNET_CRYPTO_hash_cmp (&details[i].hash_code,
&tr.hash_codes[j]))
{
found = true;
break;
}
}
if (! found)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"details[%ld].hash_code %s not found in transcript\n",
i,
GNUNET_h2s (&details[i].hash_code));
ret = json_error (output, "internal error (hash_code)");
goto END;
}
}
}
ret = replay_transcript (root,
&tr, &tr,
outcome, details,
details_len,
output); output);
} while(0);
END:
GNUNET_free (tr.prices);
GNUNET_free (tr.bidder_pub);
GNUNET_free (tr.hash_codes);
GNUNET_free (tr.expected);
return ret;
} }
/** /**
* @brief implements the TALER_Extensions.parse_policy_details interface. * @brief implements the TALER_Extensions.create_policy_details interface.
* *
* @param[in] input The policy_details for this handler during deposit * @param[in] input The policy_details for this handler during deposit
* @param[out] serial On success will contain the serial-ID under which the * @param[out] details On success will contain the details to the policy
* @param[out] deadline On success will contain a deadline, might be "forever" * @param[out] error_hint On error, will contain a hint exchange should store
* @param[out] state_on_timeout On success, will be set to the default state that the policy shall be put in case of a timeout. * the policy_details in the policy_details table.
* @param[out] error_hint On error, will contain a hint
* exchange should store the policy_details in the policy_details table.
* @return GNUNET_OK if the request was OK * @return GNUNET_OK if the request was OK
*/ */
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
auction_parse_policy_details ( auction_create_policy_details (
const json_t *input, const json_t *input,
struct GNUNET_HashCode *serial, struct TALER_PolicyDetails *details,
struct GNUNET_TIME_Timestamp *deadline,
enum TALER_PolicyFulfilmentState *state_on_timeout,
const char **error_hint) const char **error_hint)
{ {
enum GNUNET_GenericReturnValue ret = GNUNET_NO; enum GNUNET_GenericReturnValue ret = GNUNET_NO;
@ -730,13 +786,15 @@ auction_parse_policy_details (
/* We ignore the "type" field as it must have been parsed already upstream /* We ignore the "type" field as it must have been parsed already upstream
* - or this handler wouldn't have been called in first place. */ * - or this handler wouldn't have been called in first place. */
GNUNET_JSON_spec_fixed_auto ("bidder_pub", &pub), GNUNET_JSON_spec_fixed_auto ("bidder_pub", &pub),
TALER_JSON_spec_amount ("commitment",
BV_config.auction_fee.currency,
&details->commitment),
GNUNET_JSON_spec_fixed_auto ("h_auction", &hc), GNUNET_JSON_spec_fixed_auto ("h_auction", &hc),
GNUNET_JSON_spec_timestamp ("deadline", deadline), GNUNET_JSON_spec_timestamp ("deadline", &details->deadline),
GNUNET_JSON_spec_end (), GNUNET_JSON_spec_end (),
}; };
GNUNET_assert (serial); GNUNET_assert (details);
GNUNET_assert (deadline);
error_hint = NULL; error_hint = NULL;
@ -752,16 +810,13 @@ auction_parse_policy_details (
/* FIXME: check the deadline */ /* FIXME: check the deadline */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
LOG_PREFIX "check of deadline %ld not implemented!\n", LOG_PREFIX "check of deadline %ld not implemented!\n",
deadline->abs_time.abs_value_us); details->deadline.abs_time.abs_value_us);
calculate_serial (&pub, &hc, serial); calculate_hashcode (&pub, &hc, &details->hash_code);
ret = GNUNET_OK; ret = GNUNET_OK;
} while(0); } while(0);
/* In case of timeout, the coin shall be refreshable by the owner */
*state_on_timeout = TALER_PolicyFulfilmentTimeoutRefreshable;
return ret; return ret;
} }
@ -777,7 +832,7 @@ struct TALER_Extension TE_auction_brandt = {
.disable = &auction_disable, .disable = &auction_disable,
.load_config = &auction_load_config, .load_config = &auction_load_config,
.manifest = &auction_manifest, .manifest = &auction_manifest,
.parse_policy_details = &auction_parse_policy_details, .create_policy_details = &auction_create_policy_details,
.policy_get_handler = &auction_policy_get_handler, .policy_get_handler = &auction_policy_get_handler,
.policy_post_handler = &auction_policy_post_handler, .policy_post_handler = &auction_policy_post_handler,
}; };
@ -847,7 +902,7 @@ libtaler_extension_policy_brandt_vickrey_auction_init (void *arg)
LOG_PREFIX "loading... using replay_program '%s'\n", LOG_PREFIX "loading... using replay_program '%s'\n",
replay_program); replay_program);
/* TODO: read other config parameters and generate configuration */ /* FIXME: read other config parameters and generate configuration */
return &TE_auction_brandt; return &TE_auction_brandt;
} }

View File

@ -26,6 +26,7 @@
#include <gnunet/gnunet_db_lib.h> #include <gnunet/gnunet_db_lib.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_signatures.h" #include "taler_signatures.h"
#include "taler_extensions_policy.h"
/** /**
@ -223,7 +224,6 @@ enum TALER_EXCHANGEDB_ReplicatedTable
TALER_EXCHANGEDB_RT_EXTENSIONS, TALER_EXCHANGEDB_RT_EXTENSIONS,
TALER_EXCHANGEDB_RT_POLICY_DETAILS, TALER_EXCHANGEDB_RT_POLICY_DETAILS,
TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS, TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS,
TALER_EXCHANGEDB_RT_POLICY_DETAILS_FULFILMENTS,
TALER_EXCHANGEDB_RT_PURSE_REQUESTS, TALER_EXCHANGEDB_RT_PURSE_REQUESTS,
TALER_EXCHANGEDB_RT_PURSE_REFUNDS, TALER_EXCHANGEDB_RT_PURSE_REFUNDS,
TALER_EXCHANGEDB_RT_PURSE_MERGES, TALER_EXCHANGEDB_RT_PURSE_MERGES,
@ -528,24 +528,27 @@ struct TALER_EXCHANGEDB_TableData
struct struct
{ {
char *policy_options; struct GNUNET_HashCode hash_code;
struct GNUNET_HashCode serial_id; json_t *policy_json;
bool no_policy_json;
struct GNUNET_TIME_Timestamp deadline; struct GNUNET_TIME_Timestamp deadline;
uint16_t timeout_fulfilment_state; struct TALER_Amount commitment;
uint16_t fulfilment_state; struct TALER_Amount accumulated_total;
struct TALER_Amount fee;
struct TALER_Amount transferable;
uint16_t fulfillment_state; /* will also be recomputed */
uint64_t fulfillment_id;
bool no_fulfillment_id;
} policy_details; } policy_details;
struct struct
{ {
struct GNUNET_HashCode serial_id; struct GNUNET_TIME_Timestamp fulfillment_timestamp;
uint64_t fulfilment_id; char *fulfillment_proof;
} policy_details_fulfilments; struct GNUNET_HashCode h_fulfillment_proof;
struct GNUNET_HashCode *policy_hash_codes;
struct size_t policy_hash_codes_count;
{ } policy_fulfillments;
char *fulfilment_proof;
struct GNUNET_TIME_Timestamp fulfilment_timestamp;
} policy_fulfilments;
struct struct
{ {
@ -889,7 +892,6 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
struct TALER_AgeMask age_mask; struct TALER_AgeMask age_mask;
}; };
/** /**
* Signature of a function called with information about the exchange's * Signature of a function called with information about the exchange's
* denomination keys. * denomination keys.
@ -1446,37 +1448,29 @@ struct TALER_EXCHANGEDB_Deposit
char *receiver_wire_account; char *receiver_wire_account;
/** /**
* Additional details for a policy relevant for this * Additional policy and its options, relevant for this deposit operation,
* deposit operation, possibly NULL! * possibly NULL!
*/ */
json_t *policy_details; json_t *policy_json;
bool has_policy_details;
/*
* True if @e policy_json was provided
*/
bool has_policy;
/** /**
* If policy_details are present, the corresponding policy extension * Hash over the @e policy_options. Only filled if @e has_policy is true.
* calculates a serial id under which the policy_details shall be stored in * Needed for the verification of the deposit's signature
* the policy_details table.
*/
struct GNUNET_HashCode policy_serial_id;
/**
* If policy_details are present, the corresponding policy extension can set
* a deadline for this policy. Can be "forever".
*/
struct GNUNET_TIME_Timestamp policy_deadline;
/**
* The state that a _pending_ policy should be put into once the timeout triggers
*/
uint16_t policy_state_on_timeout;
/**
* Hash over the @e policy_details. Only filled if has_policy_details is
* true.
*/ */
struct TALER_ExtensionPolicyHashP h_policy; struct TALER_ExtensionPolicyHashP h_policy;
/**
* If @e policy_json was present, the corresponding policy extension
* calculates these details. These will be persisted in the policy_details
* table.
*/
struct TALER_PolicyDetails policy_details;
/** /**
* Time when this request was generated. Used, for example, to * Time when this request was generated. Used, for example, to
* assess when (roughly) the income was achieved for tax purposes. * assess when (roughly) the income was achieved for tax purposes.
@ -1549,8 +1543,8 @@ struct TALER_EXCHANGEDB_DepositListEntry
struct TALER_PrivateContractHashP h_contract_terms; struct TALER_PrivateContractHashP h_contract_terms;
/** /**
* Hash over the poliy data for this deposit * Hash over the policy data for this deposit (remains unknown to the
* (remains unknown to the Exchange). * Exchange). Needed for the verification of the deposit's signature
*/ */
struct TALER_ExtensionPolicyHashP h_policy; struct TALER_ExtensionPolicyHashP h_policy;
@ -3342,6 +3336,39 @@ struct TALER_EXCHANGEDB_Plugin
bool *conflict, bool *conflict,
bool *nonce_reuse); bool *nonce_reuse);
/**
* Retrieve the details to a policy given by its hash_code
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param hc Hash code that identifies the policy
* @param[out] detail retrieved policy details
* @return query execution status
*/
enum GNUNET_DB_QueryStatus
(*get_policy_details)(
void *cls,
const struct GNUNET_HashCode *hc,
struct TALER_PolicyDetails *detail);
/**
* Persist the policy details that extends a deposit. The particular policy
* - referenced by details->hash_code - might already exist in the table, in
* which case the call will update the contents of the record with @e details
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param details The parsed `struct TALER_PolicyDetails` according to the responsible policy extension.
* @param[out] policy_details_serial_id The ID of the entry in the policy_details table
* @param[out] accumulated_total The total amount accumulated in that policy
* @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready.
* @return query execution status
*/
enum GNUNET_DB_QueryStatus
(*persist_policy_details)(
void *cls,
const struct TALER_PolicyDetails *details,
uint64_t *policy_details_serial_id,
struct TALER_Amount *accumulated_total,
enum TALER_PolicyFulfillmentState *fulfillment_state);
/** /**
* Perform deposit operation, checking for sufficient balance * Perform deposit operation, checking for sufficient balance
@ -3351,6 +3378,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param deposit deposit operation details * @param deposit deposit operation details
* @param known_coin_id row of the coin in the known_coins table * @param known_coin_id row of the coin in the known_coins table
* @param h_payto hash of the merchant's payto URI * @param h_payto hash of the merchant's payto URI
* @param policy_details_serial_id (pointer to) the row ID of the policy details, maybe NULL
* @param[in,out] exchange_timestamp time to use for the deposit (possibly updated) * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
* @param[out] balance_ok set to true if the balance was sufficient * @param[out] balance_ok set to true if the balance was sufficient
* @param[out] in_conflict set to true if the deposit conflicted * @param[out] in_conflict set to true if the deposit conflicted
@ -3362,6 +3390,7 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_EXCHANGEDB_Deposit *deposit, const struct TALER_EXCHANGEDB_Deposit *deposit,
uint64_t known_coin_id, uint64_t known_coin_id,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
uint64_t *policy_details_serial_id,
struct GNUNET_TIME_Timestamp *exchange_timestamp, struct GNUNET_TIME_Timestamp *exchange_timestamp,
bool *balance_ok, bool *balance_ok,
bool *in_conflict); bool *in_conflict);
@ -3389,6 +3418,18 @@ struct TALER_EXCHANGEDB_Plugin
bool *zombie_required, bool *zombie_required,
bool *balance_ok); bool *balance_ok);
/**
* Add a proof of fulfillment of an policy
*
* @param cls the plugin-specific state
* @param[in,out] fulfillment The proof of fulfillment and serial_ids of the policy_details along with their new state and potential new amounts.
* @return query execution status
*/
enum GNUNET_DB_QueryStatus
(*add_policy_fulfillment_proof)(
void *cls,
struct TALER_PolicyFulfillmentTransactionData *fulfillment);
/** /**
* Check if the given @a nonce was properly locked to the given @a old_coin_pub. If so, check if we already * Check if the given @a nonce was properly locked to the given @a old_coin_pub. If so, check if we already

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA Copyright (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU General Public License as published by the Free Software
@ -25,7 +25,7 @@
#include "taler_crypto_lib.h" #include "taler_crypto_lib.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_exchangedb_plugin.h" #include "taler_extensions_policy.h"
#define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-" #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
@ -54,8 +54,8 @@ struct TALER_Extensions
}; };
/* Forward declarations */ /* Forward declarations */
enum TALER_PolicyFulfilmentState; enum TALER_PolicyFulfillmentState;
struct TALER_PolicyFulfilmentOutcome; struct TALER_PolicyFulfillmentOutcome;
/* /*
* @brief Represents the implementation of an extension. * @brief Represents the implementation of an extension.
@ -165,31 +165,23 @@ struct TALER_Extension
*/ */
/** /**
* @brief Handler to check a policy. Can be NULL; * @brief Handler to check an incoming policy and create a
* TALER_PolicyDetails. Can be NULL;
* *
* When a deposit request refers to this extension in its policy * When a deposit request refers to this extension in its policy
* (see https://docs.taler.net/core/api-exchange.html#deposit), this handler * (see https://docs.taler.net/core/api-exchange.html#deposit), this handler
* will be called before the deposit transaction. * will be called before the deposit transaction.
* *
* @param[in] policy_details Details about the policy, provided by the client * @param[in] policy_json Details about the policy, provided by the client
* during a deposit request. * during a deposit request.
* @param[out] serial On success, will contain the serial-ID under which the * @param[out] details On success, will contain the details to the policy,
* exchange should save the policy_details in the deposit table. * evaluated by the corresponding policy handler.
* @param[out] deadline On success, set to the deadline until the policy must
* be fulfilled. Might be "forever". This value is used by an
* external mechanism to detect timeouts.
* @param[out] state_on_timeout On GNUNET_OK, which state shall the
* fulfilment of this policy be put in. MUST be either
* TALER_PolicyFulfilmentTimeoutTransfer or
* TALER_PolicyFulfilmentTimeoutRefreshable
* @param[out] error_hint On error, will contain a hint * @param[out] error_hint On error, will contain a hint
* @return GNUNET_OK if the data was accepted by the extension. * @return GNUNET_OK if the data was accepted by the extension.
*/ */
enum GNUNET_GenericReturnValue (*parse_policy_details)( enum GNUNET_GenericReturnValue (*create_policy_details)(
const json_t *policy_details, const json_t *policy_json,
struct GNUNET_HashCode *serial, struct TALER_PolicyDetails *details,
struct GNUNET_TIME_Timestamp *deadline,
enum TALER_PolicyFulfilmentState *state_on_timeout,
const char **error_hint); const char **error_hint);
/** /**
@ -197,18 +189,16 @@ struct TALER_Extension
* *
* @param[in] root The JSON body from the request * @param[in] root The JSON body from the request
* @param[in] args Additional query parameters of the request. * @param[in] args Additional query parameters of the request.
* @param[in] serial_id_check Helper function to check the presence of serial_ids in policy_details. Can be used by the handler to ensure the presence of entries before starting calculations. * @param[in,out] details List of policy details related to the incoming fulfillment proof
* @param[out] outcome Result of the operation. Must be freed via TALER_policy_fulfilment_outcome_free after use. * @param[in] details_len Size of the list @e details
* @param[out] output JSON output to return to the client * @param[out] output JSON output to return to the client
* @return GNUNET_OK on success. * @return GNUNET_OK on success.
*/ */
enum GNUNET_GenericReturnValue (*policy_post_handler)( enum GNUNET_GenericReturnValue (*policy_post_handler)(
const json_t *root, const json_t *root,
const char *const args[], const char *const args[],
enum GNUNET_GenericReturnValue (*serial_id_check)(struct GNUNET_HashCode struct TALER_PolicyDetails *details,
serial_ids[], size_t size_t details_len,
size),
struct TALER_PolicyFulfilmentOutcome **outcome,
json_t **output); json_t **output);
/** /**
@ -395,163 +385,4 @@ TALER_extensions_is_age_restriction_enabled ();
struct TALER_AgeMask struct TALER_AgeMask
TALER_extensions_get_age_restriction_mask (); TALER_extensions_get_age_restriction_mask ();
/*
* ===================================
* Policy extensions related API
* ===================================
*/
/*
* @brief Describes the states of fulfilment of a policy bound to a deposit
*/
enum TALER_PolicyFulfilmentState
{
/* Initial state of the policy */
TALER_PolicyFulfilmentPending = 0,
/*
* Policy provably fulfilled. The semantics of the policy require that
* the exchange MUST transfer amount in the associated deposit to the
* payto-URI */
TALER_PolicyFulfilmentSuccessTransfer = 1,
/*
* Policy provably fulfilled. The semantics of the policy require that
* the coins' value in the associated deposit remains and the owner can
* refresh them. */
TALER_PolicyFulfilmentSuccessRefreshable = 2,
/*
* Policy provably UNfulfilled. The semantics of the policy require
* that the exchange MUST transfer amount in the associated deposit to
* the payto-URI. */
TALER_PolicyFulfilmentFailureTransfer = 3,
/*
* Policy provably UNfulfilled. The semantics of the policy require that
* the coins' value in the associated deposit remains and the owner can
* refresh them. */
TALER_PolicyFulfilmentFailureRefreshable = 4,
/*
* Policy timed out. The semantics of the policy require that the
* exchange MUST transfer amount in the associated deposit to the
* payto-URI. */
TALER_PolicyFulfilmentTimeoutTransfer = 5,
/*
* Policy timed out. The semantics of the policy require that the
* coins' value in the associated deposit remains and the owner can
* refresh them. */
TALER_PolicyFulfilmentTimeoutRefreshable = 6,
TALER_PolicyFulfilmentStateMax = TALER_PolicyFulfilmentTimeoutRefreshable
};
const char *
TALER_policy_fulfilment_state_str (enum TALER_PolicyFulfilmentState state);
/*
* @brief Respresents the outcome of a policy fulfilment evaluation
* returned by a http_post_handler.
*/
struct TALER_PolicyFulfilmentOutcome
{
size_t len;
struct TALER_PolicyFulfilmentOutcomePosition
{
/* Identifies the particular policy in the policy_details */
struct GNUNET_HashCode serial_id;
/* The state that the policy should be be put into. */
enum TALER_PolicyFulfilmentState state;
/* If @e has_new_amount is true, the actual amount to be transfered
* according to the @e state is not taken from the associated deposit
* entry, but provided with @new_amount
*/
bool has_new_amount;
struct TALER_Amount new_amount;
} *positions;
};
/*
* @brief allocate a TALER_PolicyFulfilmentOutcome
*/
struct TALER_PolicyFulfilmentOutcome *
TALER_policy_fulfilment_outcome_new (size_t len);
/*
* @brief free the content of a TALER_PolicyFulfilmentOutcome
*/
void
TALER_policy_fulfilment_outcome_free (
struct TALER_PolicyFulfilmentOutcome *outcome);
/*
* @brief Extracts meta information from policy_details
*
* @param[in] policy_details JSON of the policy detail from a deposit request
* @param[out] serial On GNUNET_OK, the hash code that should be used to save the policy_details in the policy_details table
* @param[out] deadline On GNUNET_OK, the deadline that should be saved in the policy_details table
* @param[out] state_on_timeout On GNUNET_OK, which state shall the fulfilment of this policy be put in
* @param[out] error_hint On GNUNET_SYSERR, will contain a hint for the reason why it failed
* @return GNUNET_OK on success, with *extension set to the correct extension.
* GNUNET_NO, when no extension was found. GNUNET_SYSERR when the JSON was
* invalid, with *error_hint maybe non-NULL.
*/
enum GNUNET_GenericReturnValue
TALER_extensions_extract_meta_data_from_policy_details (
const json_t *policy_details,
struct GNUNET_HashCode *serial,
struct GNUNET_TIME_Timestamp *deadline,
enum TALER_PolicyFulfilmentState *state_on_timeout,
const char **error_hint);
/*
* ================================
* Merchant refund policy
* ================================
*/
struct TALER_ExtensionPolicyMerchantRefundPolicyConfig
{
struct GNUNET_TIME_Relative max_timeout;
};
/*
* ================================
* Brandt-Vickrey Auctions policy
* ================================
*/
/*
* @brief Configuration for Brandt-Vickrey auctions policy
*/
struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig
{
uint16_t max_bidders;
uint16_t max_prices;
struct TALER_Amount auction_fee;
};
/*
* ================================
* Escrowed Payments policy
* ================================
*/
/*
* @brief Configuration for escrowed payments policy
*/
struct TALER_ExtensionPolicyEscrowedPaymentsConfig
{
struct GNUNET_TIME_Relative max_timeout;
};
#endif #endif

View File

@ -0,0 +1,199 @@
/*
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 include/taler_extensions_policy.h
* @brief Interface for policy extensions
* @author Özgür Kesim
*/
#ifndef TALER_EXTENSIONS_POLICY_H
#define TALER_EXTENSIONS_POLICY_H
#include <gnunet/gnunet_util_lib.h>
#include "taler_crypto_lib.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
/*
* @brief Describes the states of fulfillment of a policy bound to a deposit
*/
enum TALER_PolicyFulfillmentState
{
/* General error state of an fulfillment. */
TALER_PolicyFulfillmentFailure = 0,
/* The policy is not yet ready due to insufficient funding. More deposits are
* necessary for it to become ready . */
TALER_PolicyFulfillmentInsufficient = 1,
/* The policy is funded and ready, pending */
TALER_PolicyFulfillmentReady = 2,
/* Policy is provably fulfilled. */
TALER_PolicyFulfillmentSuccess = 3,
/* Policy fulfillment has timed out */
TALER_PolicyFulfillmentTimeout = 4,
TALER_PolicyFulfillmentStateCount = TALER_PolicyFulfillmentTimeout + 1
};
/*
* @brief Returns a string representation of the state of a policy fulfillment
*/
const char *
TALER_policy_fulfillment_state_str (enum TALER_PolicyFulfillmentState state);
/* @brief Details of a policy for a deposit request */
struct TALER_PolicyDetails
{
/* Hash code that should be used for the .policy_hash_code field when
* this policy is saved in the policy_details table. */
struct GNUNET_HashCode hash_code;
/* Content of the policy in its original JSON form */
const json_t *policy_json;
/* When the deadline is meat and the policy is still in "Ready" state,
* a timeout-handler will transfer the amount
* (total_amount - policy_fee - refreshable_amount)
* to the payto-URI from the corresponding deposit. The value
* amount_refreshable will be refreshable by the owner of the
* associated deposits's coins */
struct GNUNET_TIME_Timestamp deadline;
/* The amount to which this policy commits to. It must be at least as
* large as @e policy_fee. */
struct TALER_Amount commitment;
/* The total sum of contributions from coins so far to fund this
* policy. It must be at least as large as @commitment in order to be
* sufficiently funded. */
struct TALER_Amount accumulated_total;
/* The fee from the exchange for handling the policy. It is due when
* the state changes to Timeout or Success. */
struct TALER_Amount policy_fee;
/* The amount that will be transfered to the payto-URIs from the
* corresponding deposits when the fulfillment state changes to Timeout
* or Success. Note that a fulfillment handler can alter this upon
* arrival of a proof of fulfillment. The remaining amount
* (accumulated_amount - policy_amount - transferable_amount) */
struct TALER_Amount transferable_amount;
/* The state of fulfillment of a policy.
* - If the state is Insufficient, the client is required to call
* /deposit -maybe multiple times- with enough coins and the same
* policy details in order to reach the required amount. The state is
* then changed to Ready.
* - If the state changes to Timeout or Success, a handler will transfer
* the amount (total_amount - policy_fee - refreshable_amount) to the
* payto-URI from the corresponding deposit. The value
* amount_refreshable will be refreshable by the owner of the
* associated deposits's coins. */
enum TALER_PolicyFulfillmentState fulfillment_state;
/* If there is a proof of fulfillment, the row ID from the
* policy_fulfillment table */
uint64_t policy_fulfillment_id;
bool no_policy_fulfillment_id;
};
/*
* @brief All information required for the database transaction when handling a
* proof of fulfillment request.
*/
struct TALER_PolicyFulfillmentTransactionData
{
/* The incoming proof, provided by a client */
const json_t *proof;
/* The Hash of the proof */
struct GNUNET_HashCode h_proof;
/* The timestamp of retrieval of the proof */
struct GNUNET_TIME_Timestamp timestamp;
/* The ID of the proof in the policy_fulfillment table. Will be set
* during the transaction. Needed to fill the table
* policy_details_fulfillments. */
uint64_t fulfillment_id;
/* The list of policy details. Will be updated by the policy handler */
struct TALER_PolicyDetails *details;
size_t details_count;
};
/*
* @brief Extracts policy details from the deposit's policy options and the policy extensions
*
* @param[in] policy_options JSON of the policy options from a deposit request
* @param[out] details On GNUNET_OK, the parsed details
* @param[out] error_hint On GNUNET_SYSERR, will contain a hint for the reason why it failed
* @return GNUNET_OK on success, GNUNET_NO, when no extension was found. GNUNET_SYSERR when the JSON was
* invalid, with *error_hint maybe non-NULL.
*/
enum GNUNET_GenericReturnValue
TALER_extensions_create_policy_details (
const json_t *policy_options,
struct TALER_PolicyDetails *details,
const char **error_hint);
/*
* ================================
* Merchant refund policy
* ================================
*/
struct TALER_ExtensionPolicyMerchantRefundPolicyConfig
{
struct GNUNET_TIME_Relative max_timeout;
};
/*
* ================================
* Brandt-Vickrey Auctions policy
* ================================
*/
/*
* @brief Configuration for Brandt-Vickrey auctions policy
*/
struct TALER_ExtensionPolicyBrandtVickreyAuctionConfig
{
uint16_t max_bidders;
uint16_t max_prices;
struct TALER_Amount auction_fee;
};
/*
* ================================
* Escrowed Payments policy
* ================================
*/
/*
* @brief Configuration for escrowed payments policy
*/
struct TALER_ExtensionPolicyEscrowedPaymentsConfig
{
struct GNUNET_TIME_Relative max_timeout;
};
#endif