diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c index bbe257ac0..ebc38d100 100644 --- a/src/auditor/taler-auditor-sync.c +++ b/src/auditor/taler-auditor-sync.c @@ -115,7 +115,6 @@ static struct Table tables[] = { { .rt = TALER_EXCHANGEDB_RT_EXTENSIONS}, { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS }, { .rt = TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS }, - { .rt = TALER_EXCHANGEDB_RT_POLICY_DETAILS_FULFILMENTS }, { .rt = TALER_EXCHANGEDB_RT_PURSE_REQUESTS}, { .rt = TALER_EXCHANGEDB_RT_PURSE_REFUNDS}, { .rt = TALER_EXCHANGEDB_RT_PURSE_MERGES}, diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index af38b0cfa..b1650fd78 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -1626,8 +1626,8 @@ deposit_cb (void *cls, &h_wire, &deposit->h_contract_terms, &deposit->coin.h_age_commitment, - deposit->has_policy_details ? - &deposit->h_policy :NULL, &h_denom_pub, + deposit->has_policy ? &deposit->h_policy : NULL, + &h_denom_pub, deposit->timestamp, &deposit->merchant_pub, deposit->refund_deadline, diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 32cbd8dd1..1cbaee969 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1041,138 +1041,6 @@ handle_post_auditors (struct TEH_RequestContext *rc, 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. * @@ -1391,17 +1259,10 @@ handle_mhd_request (void *cls, .nargs_is_upper_bound = true }, /* 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", .method = MHD_HTTP_METHOD_POST, - .handler.post = &handle_post_extensions, + .handler.post = &TEH_extensions_post_handler, .nargs = 4, /* Arbitrary upper bound */ .nargs_is_upper_bound = true, }, diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c index d4a9666ed..14267e2af 100644 --- a/src/exchange/taler-exchange-httpd_batch-deposit.c +++ b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -90,14 +90,31 @@ struct BatchDepositContext * Additional details for policy relevant for this * deposit operation, possibly NULL! */ - json_t *policy_details; - bool has_policy_details; + json_t *policy_json; + + /** + * Will be true if policy_json were provided + */ + bool has_policy; /** * Hash over @e policy_details, might be all zero; */ 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 * assess when (roughly) the income was achieved for tax purposes. @@ -174,7 +191,7 @@ again: &TEH_keys_exchange_sign_, &bdc->h_contract_terms, &bdc->h_wire, - bdc->has_policy_details ? &bdc->h_policy : NULL, + bdc->has_policy ? &bdc->h_policy : NULL, bdc->exchange_timestamp, bdc->wire_deadline, bdc->refund_deadline, @@ -247,6 +264,21 @@ batch_deposit_transaction (void *cls, bool balance_ok; 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; inum_coins; i++) { const struct TALER_EXCHANGEDB_Deposit *deposit = &dc->deposits[i]; @@ -258,13 +290,19 @@ batch_deposit_transaction (void *cls, mhd_ret); if (qs < 0) return qs; - qs = TEH_plugin->do_deposit (TEH_plugin->cls, - deposit, - known_coin_id, - &dc->h_payto, - &dc->exchange_timestamp, - &balance_ok, - &in_conflict); + + qs = TEH_plugin->do_deposit ( + TEH_plugin->cls, + deposit, + known_coin_id, + &dc->h_payto, + (dc->has_policy) + ? &dc->policy_details_serial_id + : NULL, + &dc->exchange_timestamp, + &balance_ok, + &in_conflict); + if (qs < 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -474,7 +512,7 @@ parse_coin (struct MHD_Connection *connection, &dc->h_wire, &dc->h_contract_terms, &deposit->coin.h_age_commitment, - dc->has_policy_details ? &dc->h_policy : + dc->has_policy ? &dc->h_policy : NULL, &deposit->coin.denom_pub_hash, dc->timestamp, @@ -501,7 +539,7 @@ parse_coin (struct MHD_Connection *connection, but rather insert them ONCE and then per-coin only use the resulting extension UUID/serial; so the data structure 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->refund_deadline = dc->refund_deadline; deposit->wire_deadline = dc->wire_deadline; @@ -518,7 +556,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, struct BatchDepositContext dc; json_t *coins; bool no_refund_deadline = true; - bool no_policy_details = true; + bool no_policy_json = true; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("merchant_payto_uri", &dc.payto_uri), @@ -532,8 +570,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, &coins), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("policy", - &dc.policy_details), - &no_policy_details), + &dc.policy_json), + &no_policy_json), GNUNET_JSON_spec_timestamp ("timestamp", &dc.timestamp), GNUNET_JSON_spec_mark_optional ( @@ -564,7 +602,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, 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) */ { @@ -610,11 +648,26 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, TALER_merchant_wire_signature_hash (dc.payto_uri, &dc.wire_salt, &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.num_coins = json_array_size (coins); if (0 == dc.num_coins) { @@ -651,6 +704,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc, GNUNET_JSON_parse_free (spec); 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 (); diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 12741c6b5..f44775c09 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -132,6 +132,12 @@ struct DepositContext */ 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) 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); + + /* 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->known_coin_id, + &dc->h_payto, + (dc->deposit->has_policy) + ? &dc->policy_details_serial_id + : NULL, + &dc->exchange_timestamp, + &balance_ok, + &in_conflict); - qs = TEH_plugin->do_deposit (TEH_plugin->cls, - dc->deposit, - dc->known_coin_id, - &dc->h_payto, - &dc->exchange_timestamp, - &balance_ok, - &in_conflict); if (qs < 0) { if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -219,7 +247,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, struct TALER_EXCHANGEDB_Deposit deposit; const char *payto_uri; struct TALER_ExtensionPolicyHashP *ph_policy = NULL; - bool no_policy_details; + bool no_policy_json; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("merchant_payto_uri", &payto_uri), @@ -255,8 +283,8 @@ TEH_handler_deposit (struct MHD_Connection *connection, &deposit.wire_deadline), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("policy", - &deposit.policy_details), - &no_policy_details), + &deposit.policy_json), + &no_policy_json), GNUNET_JSON_spec_end () }; 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 */ - deposit.has_policy_details = ! no_policy_details; + deposit.has_policy = ! no_policy_json; /* validate merchant's wire details (as far as we can) */ { @@ -333,28 +361,6 @@ TEH_handler_deposit (struct MHD_Connection *connection, &h_wire); 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 */ dc.exchange_timestamp = GNUNET_TIME_timestamp_get (); /* check denomination exists and is valid */ @@ -420,13 +426,6 @@ TEH_handler_deposit (struct MHD_Connection *connection, 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; /* check coin signature */ switch (dk->denom_pub.cipher) @@ -463,6 +462,27 @@ TEH_handler_deposit (struct MHD_Connection *connection, 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]++; if (GNUNET_OK != TALER_wallet_deposit_verify (&deposit.amount_with_fee, diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index e8d3e4aff..8e8f0e019 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -14,7 +14,7 @@ */ /** * @file taler-exchange-httpd_extensions.c - * @brief Handle extensions (age-restriction, peer2peer) + * @brief Handle extensions (age-restriction, policy extensions) * @author Özgür Kesim */ #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 */ diff --git a/src/exchange/taler-exchange-httpd_extensions.h b/src/exchange/taler-exchange-httpd_extensions.h index 4659b653e..e435f8f03 100644 --- a/src/exchange/taler-exchange-httpd_extensions.h +++ b/src/exchange/taler-exchange-httpd_extensions.h @@ -40,4 +40,19 @@ TEH_extensions_init (void); 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 diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h index c1b7326ff..5acd3605e 100644 --- a/src/exchange/taler-exchange-httpd_metrics.h +++ b/src/exchange/taler-exchange-httpd_metrics.h @@ -44,7 +44,8 @@ enum TEH_MetricTypeRequest TEH_MT_REQUEST_IDEMPOTENT_MELT = 10, TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11, 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! */ }; /** diff --git a/src/exchangedb/exchange-0001-part.sql b/src/exchangedb/exchange-0001-part.sql index 4a116d127..c57eb4547 100644 --- a/src/exchangedb/exchange-0001-part.sql +++ b/src/exchangedb/exchange-0001-part.sql @@ -536,76 +536,69 @@ CREATE TABLE IF NOT EXISTS refresh_transfer_keys_default SELECT add_constraints_to_refresh_transfer_keys_partition('default'); --- ------------------------------ policy_fulfilments ------------------------------------- +-- ------------------------------ policy_fulfillments ------------------------------------- -CREATE TABLE IF NOT EXISTS policy_fulfilments - (fulfilment_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY - ,fulfilment_timestamp INT8 NOT NULL - ,fulfilment_proof VARCHAR) - PARTITION BY HASH (fulfilment_id); -COMMENT ON TABLE policy_fulfilments - IS 'Proofs of fulfilment of policies that were set in deposits'; -COMMENT ON COLUMN policy_fulfilments.fulfilment_timestamp - IS 'Timestamp of the arrival of a proof of fulfilment'; -COMMENT ON COLUMN policy_fulfilments.fulfilment_proof - IS 'JSON object with a proof of the fulfilment of a policy. Supported details depend on the policy extensions supported by the exchange.'; - -CREATE TABLE IF NOT EXISTS policy_fulfilments_default - PARTITION OF policy_fulfilments - FOR VALUES WITH (MODULUS 1, REMAINDER 0); +CREATE TABLE IF NOT EXISTS policy_fulfillments + (fulfillment_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE PRIMARY KEY + ,fulfillment_timestamp INT8 NOT NULL + ,fulfillment_proof VARCHAR + ,h_fulfillment_proof BYTEA NOT NULL CHECK(LENGTH(h_fulfillment_proof) = 64) UNIQUE + ,policy_hash_codes BYTEA NOT NULL CHECK(0 = MOD(LENGTH(policy_hash_codes), 16)) + ); +COMMENT ON TABLE policy_fulfillments + IS 'Proofs of fulfillment of policies that were set in deposits'; +COMMENT ON COLUMN policy_fulfillments.fulfillment_timestamp + IS 'Timestamp of the arrival of a proof of fulfillment'; +COMMENT ON COLUMN policy_fulfillments.fulfillment_proof + IS 'JSON object with a proof of the fulfillment of a policy. Supported details depend on the policy extensions supported by the exchange.'; +COMMENT ON COLUMN policy_fulfillments.h_fulfillment_proof + 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 ---------------------------------------- CREATE TABLE IF NOT EXISTS policy_details (policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY - ,serial_id BYTEA PRIMARY KEY CHECK(LENGTH(serial_id)=64) - ,policy_options VARCHAR + ,policy_hash_code BYTEA PRIMARY KEY CHECK(LENGTH(policy_hash_code)=16) + ,policy_json VARCHAR ,deadline INT8 NOT NULL - ,timeout_fulfilment_state smallint NOT NULL CHECK(timeout_fulfilment_state in (5, 6)) - ,fulfilment_state smallint NOT NULL CHECK(fulfilment_state between 0 and 6)) - PARTITION BY HASH (serial_id); + ,commitment_val INT8 NOT NULL + ,commitment_frac INT4 NOT NULL + ,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 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'; -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.'; COMMENT ON COLUMN policy_details.deadline - IS 'Deadline until the policy must be marked as fulfilled or unfulfilled (maybe "forever")'; -COMMENT ON COLUMN policy_details.timeout_fulfilment_state - 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)'; -COMMENT ON COLUMN policy_details.fulfilment_state - IS 'State of the fulfilment: - - 0 (PENDING) - - 1 (SUCCESS, transfer coins) - - 2 (SUCCESS, coins refreshable) - - 3 (FAILURE, transfer coins) - - 4 (FAILURE, coins refreshable) - - 5 (TIMEOUT, tranfer coins) - - 6 (TIMEOUT, coins refrehsable)'; - -CREATE TABLE IF NOT EXISTS policy_details_default - PARTITION OF policy_details - FOR VALUES WITH (MODULUS 1, REMAINDER 0); - --- ------------------------------ policy_details_fulfilments ----------------------------- - -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); - + IS 'Deadline until the policy must be marked as fulfilled (maybe "forever")'; +COMMENT ON COLUMN policy_details.commitment_val + IS 'The amount that this policy commits to. Invariant: commitment >= fee'; +COMMENT ON COLUMN policy_details.accumulated_total_val + 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'; +COMMENT ON COLUMN policy_details.fee_val + IS 'The fee for this policy, due when the policy is fulfilled or timed out'; +COMMENT ON COLUMN policy_details.transferable_val + 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.'; +COMMENT ON COLUMN policy_details.fulfillment_state + IS 'State of the fulfillment: + - 0 (Failure) + - 1 (Insufficient) + - 2 (Ready) + - 4 (Success) + - 5 (Timeout)'; +COMMENT ON COLUMN policy_details.fulfillment_id + 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.'; -- ------------------------------ deposits ---------------------------------------- diff --git a/src/exchangedb/irbt_callbacks.c b/src/exchangedb/irbt_callbacks.c index e9cce43cb..b526b5cbd 100644 --- a/src/exchangedb/irbt_callbacks.c +++ b/src/exchangedb/irbt_callbacks.c @@ -929,16 +929,18 @@ irbt_cb_table_policy_details (struct PostgresClosure *pg, { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&td->serial), - NULL == td->details.policy_details.policy_options ? - GNUNET_PQ_query_param_null () : - GNUNET_PQ_query_param_string ( - td->details.policy_details.policy_options), + ((NULL == td->details.policy_details.policy_json) || + (td->details.policy_details.no_policy_json)) + ? GNUNET_PQ_query_param_null () + : TALER_PQ_query_param_json (td->details.policy_details.policy_json), GNUNET_PQ_query_param_timestamp ( &td->details.policy_details.deadline), GNUNET_PQ_query_param_uint16 ( - &td->details.policy_details.timeout_fulfilment_state), - GNUNET_PQ_query_param_uint16 ( - &td->details.policy_details.fulfilment_state), + &td->details.policy_details.fulfillment_state), + (td->details.policy_details.no_fulfillment_id) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_uint64 ( + &td->details.policy_details.fulfillment_id), GNUNET_PQ_query_param_end }; diff --git a/src/exchangedb/lrbt_callbacks.c b/src/exchangedb/lrbt_callbacks.c index 5fe0817e0..d24dc688b 100644 --- a/src/exchangedb/lrbt_callbacks.c +++ b/src/exchangedb/lrbt_callbacks.c @@ -1435,33 +1435,47 @@ lrbt_cb_table_policy_details (void *cls, unsigned int num_results) { struct LookupRecordsByTableContext *ctx = cls; + struct PostgresClosure *pg = ctx->pg; struct TALER_EXCHANGEDB_TableData td = { .table = TALER_EXCHANGEDB_RT_POLICY_DETAILS }; for (unsigned int i = 0; ierror = true; - return; - } - ctx->cb (ctx->cb_cls, - &td); - GNUNET_PQ_cleanup_result (rs); - } -} - - /** * Function called with purse_requests table entries. * diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 415417cbc..062133e6a 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -822,8 +822,8 @@ prepare_statements (struct PostgresClosure *pg) ",out_balance_ok AS balance_ok" ",out_conflict AS conflicted" " FROM exchange_do_deposit" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20);", - 20), + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17);", + 17), /* used in postgres_do_purse_deposit() */ GNUNET_PQ_make_prepare ( "call_purse_deposit", @@ -3927,37 +3927,45 @@ prepare_statements (struct PostgresClosure *pg) "($1, $2, $3);", 3), GNUNET_PQ_make_prepare ( - "insert_into_table_policy_details", - "INSERT INTO policy_details" - "(policy_details_serial_id" - ",serial_id" - ",policy_options" - ",deadline" - ",timeout_fulfilment_state" - ",fulfilment_state" - ") VALUES " - "($1, $2, $3, $4, $5, $6);", - 6), + "call_insert_or_update_policy_details", + "SELECT " + " out_policy_details_serial_id as policy_serial_id" + ",out_accumulated_total_val as accumulated_total_val" + ",out_accumulated_total_frac as accumulated_total_frac" + ",out_fulfillment_state as fulfillment_state" + " FROM insert_or_update_policy_details" + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);", + 12), GNUNET_PQ_make_prepare ( - "insert_into_table_policy_fulfilments", - "INSERT INTO policy_fulfilments" - "(fulfilment_id" - ",fulfilment_timestamp" - ",fulfilment_proof" + "insert_proof_into_policy_fulfillments", + "INSERT INTO policy_fulfillments " + "(fulfillment_timestamp " + ",fulfillment_proof" + ",h_fulfillment_proof" + ",policy_hash_codes" ") VALUES " - "($1, $2, $3);", - 3), + "($1, $2, $3, $4) " + "RETURNING fulfillment_id;", + 4), GNUNET_PQ_make_prepare ( - "insert_into_table_policy_details_fulfilments", - "INSERT INTO policy_details_fulfilments" - "(fulfilment_id" - ",serial_id" - ") VALUES " - "($1, $2);", - 2), + "update_policy_details", + "UPDATE policy_details " + "SET " + " deadline=$2," + " commitment_val=$3," + " commitment_frac=$4," + " 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 ( "insert_into_table_purse_requests", - "INSERT INTO purse_requests" + "INSERT INTO purse_requests " "(purse_requests_serial_id" ",purse_pub" ",merge_pub" @@ -6250,7 +6258,7 @@ compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub) * @param deposit deposit operation details * @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 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[out] balance_ok set to true if the balance was sufficient * @param[out] in_conflict set to true if the deposit conflicted @@ -6262,6 +6270,7 @@ postgres_do_deposit ( const struct TALER_EXCHANGEDB_Deposit *deposit, uint64_t known_coin_id, const struct TALER_PaytoHashP *h_payto, + uint64_t *policy_details_serial_id, struct GNUNET_TIME_Timestamp *exchange_timestamp, bool *balance_ok, 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->csig), GNUNET_PQ_query_param_uint64 (&deposit_shard), - GNUNET_PQ_query_param_bool (deposit->has_policy_details), - (deposit->has_policy_details) - ? TALER_PQ_query_param_json (deposit->policy_details) - : GNUNET_PQ_query_param_null (), - (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_bool (deposit->has_policy), + (NULL == policy_details_serial_id) + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_uint64 (policy_details_serial_id), GNUNET_PQ_query_param_end }; 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 * 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. */ @@ -14470,8 +14677,8 @@ postgres_lookup_records_by_table (void *cls, rh = &lrbt_cb_table_policy_details; break; case TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS: - statement = "select_above_serial_by_table_policy_fulfilments"; - rh = &lrbt_cb_table_policy_fulfilments; + statement = "select_above_serial_by_table_policy_fulfillments"; + rh = &lrbt_cb_table_policy_fulfillments; break; case TALER_EXCHANGEDB_RT_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: rh = &irbt_cb_table_policy_details; break; - /* TODO: policy_details_fulfilments and policy_fulfilments */ + /* TODO: policy_details_fulfillments and policy_fulfillments */ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS: rh = &irbt_cb_table_purse_requests; break; @@ -17110,7 +17317,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->do_withdraw = &postgres_do_withdraw; plugin->do_batch_withdraw = &postgres_do_batch_withdraw; 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->add_policy_fulfillment_proof = &postgres_add_policy_fulfillment_proof; plugin->do_melt = &postgres_do_melt; plugin->do_refund = &postgres_do_refund; plugin->do_recoup = &postgres_do_recoup; diff --git a/src/exchangedb/procedures.sql b/src/exchangedb/procedures.sql index aeed5de03..85accfc2f 100644 --- a/src/exchangedb/procedures.sql +++ b/src/exchangedb/procedures.sql @@ -511,10 +511,7 @@ CREATE OR REPLACE FUNCTION exchange_do_deposit( IN in_coin_sig BYTEA, IN in_shard INT8, IN in_policy_blocked BOOLEAN, - IN in_policy_details VARCHAR, - IN in_policy_serial_id BYTEA, - IN in_policy_deadline SMALLINT, - IN in_policy_timeout_fulfilment_state SMALLINT, + IN in_policy_details_serial_id INT8, OUT out_exchange_timestamp INT8, OUT out_balance_ok BOOLEAN, OUT out_conflict BOOLEAN) @@ -522,34 +519,11 @@ LANGUAGE plpgsql AS $$ DECLARE wtsi INT8; -- wire target serial id -DECLARE - xdi INT8; -- eXstension details serial id BEGIN --- Shards: INSERT policy_details (by policy_details_serial_id) --- INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING; +-- Shards: INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING; -- INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING; -- 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 (wire_target_h_payto ,payto_uri) @@ -602,7 +576,8 @@ INSERT INTO exchange.deposits ,in_wire_salt ,in_h_payto ,in_policy_blocked - ,xdi) + ,in_policy_details_serial_id + ) ON CONFLICT DO NOTHING; IF NOT FOUND @@ -622,6 +597,7 @@ THEN AND wire_target_h_payto=in_h_payto AND coin_pub=in_coin_pub 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 THEN @@ -2211,5 +2187,123 @@ BEGIN 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; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index 6acf01365..e0ad23b24 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -1500,6 +1500,7 @@ run (void *cls) &deposit, known_coin_id, &h_payto, + NULL, /* no policy_details_serial_id */ &deposit_timestamp, &balance_ok, &in_conflict)); diff --git a/src/extensions/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c index 3c38d0f8a..581242503 100644 --- a/src/extensions/age_restriction/age_restriction.c +++ b/src/extensions/age_restriction/age_restriction.c @@ -153,7 +153,7 @@ struct TALER_Extension TE_age_restriction = { .manifest = &age_restriction_manifest, /* This extension is not a policy extension */ - .parse_policy_details = NULL, + .create_policy_details = NULL, .policy_get_handler = NULL, .policy_post_handler = NULL, }; diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index cfc109400..2ed973d92 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -357,31 +357,27 @@ TALER_extensions_load_manifests ( * Policy related */ -static char *fulfilment2str[] = { - [TALER_PolicyFulfilmentPending] = "Pending", - [TALER_PolicyFulfilmentSuccessTransfer] = "SuccessTransfer", - [TALER_PolicyFulfilmentSuccessRefreshable] = "SuccessRefreshable", - [TALER_PolicyFulfilmentFailureTransfer] = "FailureTransfer", - [TALER_PolicyFulfilmentFailureRefreshable] = "FailureRefreshable", - [TALER_PolicyFulfilmentTimeoutTransfer] = "TimeoutTransfer", - [TALER_PolicyFulfilmentTimeoutRefreshable] = "TimeoutRefreshable", +static char *fulfillment2str[] = { + [TALER_PolicyFulfillmentReady] = "Ready", + [TALER_PolicyFulfillmentSuccess] = "Success", + [TALER_PolicyFulfillmentFailure] = "Failure", + [TALER_PolicyFulfillmentTimeout] = "Timeout", + [TALER_PolicyFulfillmentInsufficient] = "Insufficient", }; const char * -TALER_policy_fulfilment_state_str ( - enum TALER_PolicyFulfilmentState state) +TALER_policy_fulfillment_state_str ( + enum TALER_PolicyFulfillmentState state) { - GNUNET_assert (TALER_PolicyFulfilmentStateMax >= state); - return fulfilment2str[state]; + GNUNET_assert (TALER_PolicyFulfillmentStateCount > state); + return fulfillment2str[state]; } 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_deadline, +TALER_extensions_create_policy_details ( + const json_t *policy_options, + struct TALER_PolicyDetails *details, const char **error_hint) { enum GNUNET_GenericReturnValue ret; @@ -391,14 +387,14 @@ TALER_extensions_extract_meta_data_from_policy_details ( *error_hint = NULL; - if ((NULL == policy_details) || - (! json_is_object (policy_details))) + if ((NULL == policy_options) || + (! json_is_object (policy_options))) { *error_hint = "invalid policy object"; return GNUNET_SYSERR; } - jtype = json_object_get (policy_details, "type"); + jtype = json_object_get (policy_options, "type"); if (NULL == jtype) { *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); if ((NULL == extension) || - (NULL == extension->parse_policy_details)) + (NULL == extension->create_policy_details)) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -423,50 +419,13 @@ TALER_extensions_extract_meta_data_from_policy_details ( return GNUNET_NO; } - *deadline = GNUNET_TIME_UNIT_FOREVER_TS; - ret = extension->parse_policy_details (policy_details, - serial, - deadline, - state_on_deadline, - error_hint); - - GNUNET_assert ((TALER_PolicyFulfilmentTimeoutRefreshable == - *state_on_deadline) || - (TALER_PolicyFulfilmentTimeoutTransfer == - *state_on_deadline)); - + details->deadline = GNUNET_TIME_UNIT_FOREVER_TS; + ret = extension->create_policy_details (policy_options, + details, + error_hint); 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 */ diff --git a/src/extensions/policy_brandt_vickrey_auction/policy_brandt_vickrey_auction.c b/src/extensions/policy_brandt_vickrey_auction/policy_brandt_vickrey_auction.c index bfd7b813f..34c84bb48 100644 --- a/src/extensions/policy_brandt_vickrey_auction/policy_brandt_vickrey_auction.c +++ b/src/extensions/policy_brandt_vickrey_auction/policy_brandt_vickrey_auction.c @@ -31,7 +31,7 @@ #define MAX_RESULT_SIZE 10 * 1024 /* (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 = { .max_bidders = 10, .max_prices = 10, @@ -82,8 +82,8 @@ struct transcript /* Hash of the auction */ struct GNUNET_HashCode h_auction; - /* (n-1) calculated serial_ids */ - struct GNUNET_HashCode *serial_ids; + /* (n-1) calculated hash_codes */ + struct GNUNET_HashCode *hash_codes; /* Type of auction, see libbrandt */ @@ -146,7 +146,7 @@ json_error (json_t **output, /* Create serial as H(bidder_pub, h_auction) */ static void -calculate_serial ( +calculate_hashcode ( struct GNUNET_CRYPTO_EddsaPublicKey *pub, struct GNUNET_HashCode *hc, struct GNUNET_HashCode *serial) @@ -171,7 +171,7 @@ calculate_serial ( * @param[out] jerror JSON output for errors * @return GNUNET_OK on succes * - * TODO: + * FIXME: * - parse and verify signatures */ static enum GNUNET_GenericReturnValue @@ -181,7 +181,7 @@ parse_transcript (const json_t *jtr, { json_t *auc; - // TODO: struct GNUNET_CRYPTO_EddsaSignature sig; + // FIXME: struct GNUNET_CRYPTO_EddsaSignature sig; GNUNET_assert (jtr); GNUNET_assert (tr); @@ -272,7 +272,7 @@ parse_transcript (const json_t *jtr, tr->n, struct GNUNET_CRYPTO_EddsaPublicKey); - tr->serial_ids = GNUNET_new_array ( + tr->hash_codes = GNUNET_new_array ( tr->n, struct GNUNET_HashCode); @@ -284,7 +284,7 @@ parse_transcript (const json_t *jtr, GNUNET_JSON_spec_end (), }; - /* TODO: cleanup */ + /* FIXME: cleanup */ if (GNUNET_OK != GNUNET_JSON_parse (val, spec, @@ -294,9 +294,9 @@ parse_transcript (const json_t *jtr, "bidder no %ld public key couldn't be parsed", idx + 1); - calculate_serial (&tr->bidder_pub[idx], - &tr->h_auction, - &tr->serial_ids[idx]); + calculate_hashcode (&tr->bidder_pub[idx], + &tr->h_auction, + &tr->hash_codes[idx]); } } @@ -316,7 +316,7 @@ parse_transcript (const json_t *jtr, return json_error (jerror, "not the right no. of messages found"); - /* TODO: parse and evaluate signatures */ + /* FIXME: parse and evaluate signatures */ } // 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: @@ -373,16 +373,18 @@ DONE: * * @param[in] root The original JSON transcript * @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 * @return GNUNET_OK on success * - * TODO: Make this resumable + * FIXME: Make this resumable */ static enum GNUNET_GenericReturnValue replay_transcript (const json_t*root, struct transcript *tr, - struct TALER_PolicyFulfilmentOutcome **outcome, + struct TALER_PolicyDetails *details, + size_t details_count, json_t **result) { enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; @@ -488,30 +490,33 @@ replay_transcript (const json_t*root, 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; 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; in; 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 */ json_array_foreach (winners, idx, w) { + // TODO enum GNUNET_GenericReturnValue ret; const char *jerror; uint16_t bidder; @@ -540,7 +545,7 @@ replay_transcript (const json_t*root, LOG_PREFIX "couldn't parse output of replay program: %s\n", jerror); - ret = json_error (result, "internal error"); + ret = json_error (result, "internal error (replay)"); goto DONE; } @@ -549,19 +554,41 @@ replay_transcript (const json_t*root, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, LOG_PREFIX "replay program sent a bidder out of range\n"); - ret = json_error (result, "internal error"); + ret = json_error (result, "internal error (bidder)"); goto DONE; } - // Fill the outcome position for this winning bidder. - pos = &((*outcome)->positions[idx]); - pos->has_new_amount = true; - pos->new_amount = price; - pos->state = TALER_PolicyFulfilmentSuccessTransfer; + // Fill the details for this winning bidder. + { + struct TALER_PolicyDetails *det = NULL; + struct GNUNET_HashCode *hc = &tr->hash_codes[idx]; + + /* 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; + } + } + + if (NULL == det) + { + 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; + } + + } } - // TODO: return own result object. - // TODO: sign the result by the exchange + // FIXME: return own result object. + // FIXME: sign the result by the exchange *result = json_copy (res); json_decref (res); } @@ -582,11 +609,6 @@ replay_transcript (const json_t*root, } DONE: - if (GNUNET_OK != ret) - { - TALER_policy_fulfilment_outcome_free (*outcome); - *outcome = NULL; - } return ret; } @@ -642,7 +664,7 @@ auction_load_config ( struct TALER_Extension *ext, json_t *jconfig) { - /* TODO: parse configuration */ + /* FIXME: parse configuration */ ext->enabled = true; return GNUNET_OK; } @@ -656,7 +678,7 @@ auction_policy_get_handler ( struct MHD_Connection *connection, 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, 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 * - * TODO: make this non-blocking + * FIXME: make this non-blocking */ enum GNUNET_GenericReturnValue auction_policy_post_handler ( const json_t *root, const char *const args[], - enum GNUNET_GenericReturnValue (*serial_id_check)(struct GNUNET_HashCode - serial_ids[], size_t size), - struct TALER_PolicyFulfilmentOutcome **outcome, + struct TALER_PolicyDetails *details, + size_t details_len, json_t **output) { - struct transcript tr = {}; + struct transcript tr = {0}; enum GNUNET_GenericReturnValue ret; - *outcome = NULL; - ret = parse_transcript (root, - &tr, - output); - - /* TODO: cleanups! */ - if (GNUNET_OK != ret) - return ret; - - serial_id_check (tr.serial_ids, tr.n); - - return replay_transcript (root, + do { + ret = parse_transcript (root, &tr, - outcome, output); + + if (GNUNET_OK != ret) + break; + + /* 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; + } + + for (size_t i = 0; icommitment), 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_assert (serial); - GNUNET_assert (deadline); + GNUNET_assert (details); error_hint = NULL; @@ -752,16 +810,13 @@ auction_parse_policy_details ( /* FIXME: check the deadline */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 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; } while(0); - /* In case of timeout, the coin shall be refreshable by the owner */ - *state_on_timeout = TALER_PolicyFulfilmentTimeoutRefreshable; - return ret; } @@ -777,7 +832,7 @@ struct TALER_Extension TE_auction_brandt = { .disable = &auction_disable, .load_config = &auction_load_config, .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_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", replay_program); - /* TODO: read other config parameters and generate configuration */ + /* FIXME: read other config parameters and generate configuration */ return &TE_auction_brandt; } diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 80a19b676..881e0cefd 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -26,6 +26,7 @@ #include #include "taler_json_lib.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_POLICY_DETAILS, TALER_EXCHANGEDB_RT_POLICY_FULFILMENTS, - TALER_EXCHANGEDB_RT_POLICY_DETAILS_FULFILMENTS, TALER_EXCHANGEDB_RT_PURSE_REQUESTS, TALER_EXCHANGEDB_RT_PURSE_REFUNDS, TALER_EXCHANGEDB_RT_PURSE_MERGES, @@ -528,24 +528,27 @@ struct TALER_EXCHANGEDB_TableData struct { - char *policy_options; - struct GNUNET_HashCode serial_id; + struct GNUNET_HashCode hash_code; + json_t *policy_json; + bool no_policy_json; struct GNUNET_TIME_Timestamp deadline; - uint16_t timeout_fulfilment_state; - uint16_t fulfilment_state; + struct TALER_Amount commitment; + 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; struct { - struct GNUNET_HashCode serial_id; - uint64_t fulfilment_id; - } policy_details_fulfilments; - - struct - { - char *fulfilment_proof; - struct GNUNET_TIME_Timestamp fulfilment_timestamp; - } policy_fulfilments; + struct GNUNET_TIME_Timestamp fulfillment_timestamp; + char *fulfillment_proof; + struct GNUNET_HashCode h_fulfillment_proof; + struct GNUNET_HashCode *policy_hash_codes; + size_t policy_hash_codes_count; + } policy_fulfillments; struct { @@ -889,7 +892,6 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData struct TALER_AgeMask age_mask; }; - /** * Signature of a function called with information about the exchange's * denomination keys. @@ -1446,37 +1448,29 @@ struct TALER_EXCHANGEDB_Deposit char *receiver_wire_account; /** - * Additional details for a policy relevant for this - * deposit operation, possibly NULL! + * Additional policy and its options, relevant for this deposit operation, + * possibly NULL! */ - json_t *policy_details; - bool has_policy_details; + json_t *policy_json; + + /* + * True if @e policy_json was provided + */ + bool has_policy; /** - * If policy_details are present, the corresponding policy extension - * calculates a serial id under which the policy_details shall be stored in - * 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. + * Hash over the @e policy_options. Only filled if @e has_policy is true. + * Needed for the verification of the deposit's signature */ 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 * assess when (roughly) the income was achieved for tax purposes. @@ -1549,8 +1543,8 @@ struct TALER_EXCHANGEDB_DepositListEntry struct TALER_PrivateContractHashP h_contract_terms; /** - * Hash over the poliy data for this deposit - * (remains unknown to the Exchange). + * Hash over the policy data for this deposit (remains unknown to the + * Exchange). Needed for the verification of the deposit's signature */ struct TALER_ExtensionPolicyHashP h_policy; @@ -3342,6 +3336,39 @@ struct TALER_EXCHANGEDB_Plugin bool *conflict, 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 @@ -3351,6 +3378,7 @@ struct TALER_EXCHANGEDB_Plugin * @param deposit deposit operation details * @param known_coin_id row of the coin in the known_coins table * @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[out] balance_ok set to true if the balance was sufficient * @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, uint64_t known_coin_id, const struct TALER_PaytoHashP *h_payto, + uint64_t *policy_details_serial_id, struct GNUNET_TIME_Timestamp *exchange_timestamp, bool *balance_ok, bool *in_conflict); @@ -3389,6 +3418,18 @@ struct TALER_EXCHANGEDB_Plugin bool *zombie_required, 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 diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index 5a5841e52..24e0366d8 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -1,6 +1,6 @@ /* 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 terms of the GNU General Public License as published by the Free Software @@ -25,7 +25,7 @@ #include "taler_crypto_lib.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" -#include "taler_exchangedb_plugin.h" +#include "taler_extensions_policy.h" #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-" @@ -54,8 +54,8 @@ struct TALER_Extensions }; /* Forward declarations */ -enum TALER_PolicyFulfilmentState; -struct TALER_PolicyFulfilmentOutcome; +enum TALER_PolicyFulfillmentState; +struct TALER_PolicyFulfillmentOutcome; /* * @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 * (see https://docs.taler.net/core/api-exchange.html#deposit), this handler * 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. - * @param[out] serial On success, will contain the serial-ID under which the - * exchange should save the policy_details in the deposit table. - * @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] details On success, will contain the details to the policy, + * evaluated by the corresponding policy handler. * @param[out] error_hint On error, will contain a hint * @return GNUNET_OK if the data was accepted by the extension. */ - enum GNUNET_GenericReturnValue (*parse_policy_details)( - const json_t *policy_details, - struct GNUNET_HashCode *serial, - struct GNUNET_TIME_Timestamp *deadline, - enum TALER_PolicyFulfilmentState *state_on_timeout, + enum GNUNET_GenericReturnValue (*create_policy_details)( + const json_t *policy_json, + struct TALER_PolicyDetails *details, const char **error_hint); /** @@ -197,18 +189,16 @@ struct TALER_Extension * * @param[in] root The JSON body from 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[out] outcome Result of the operation. Must be freed via TALER_policy_fulfilment_outcome_free after use. + * @param[in,out] details List of policy details related to the incoming fulfillment proof + * @param[in] details_len Size of the list @e details * @param[out] output JSON output to return to the client * @return GNUNET_OK on success. */ enum GNUNET_GenericReturnValue (*policy_post_handler)( const json_t *root, const char *const args[], - enum GNUNET_GenericReturnValue (*serial_id_check)(struct GNUNET_HashCode - serial_ids[], size_t - size), - struct TALER_PolicyFulfilmentOutcome **outcome, + struct TALER_PolicyDetails *details, + size_t details_len, json_t **output); /** @@ -395,163 +385,4 @@ TALER_extensions_is_age_restriction_enabled (); struct TALER_AgeMask 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 diff --git a/src/include/taler_extensions_policy.h b/src/include/taler_extensions_policy.h new file mode 100644 index 000000000..cd75f2d4d --- /dev/null +++ b/src/include/taler_extensions_policy.h @@ -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 + */ +/** + * @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 +#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