diff options
Diffstat (limited to 'src/exchangedb')
-rw-r--r-- | src/exchangedb/exchange-0001-part.sql | 111 | ||||
-rw-r--r-- | src/exchangedb/irbt_callbacks.c | 16 | ||||
-rw-r--r-- | src/exchangedb/lrbt_callbacks.c | 106 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 302 | ||||
-rw-r--r-- | src/exchangedb/procedures.sql | 152 | ||||
-rw-r--r-- | src/exchangedb/test_exchangedb.c | 1 |
6 files changed, 478 insertions, 210 deletions
diff --git a/src/exchangedb/exchange-0001-part.sql b/src/exchangedb/exchange-0001-part.sql index 4a116d12..c57eb454 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 ------------------------------------- - -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); +-- ------------------------------ policy_fulfillments ------------------------------------- + +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 e9cce43c..b526b5cb 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 5fe0817e..d24dc688 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; i<num_results; i++) { - bool no_config = false; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id", &td.serial), - GNUNET_PQ_result_spec_auto_from_type ("serial_id", + GNUNET_PQ_result_spec_auto_from_type ("hash_code", &td.details.policy_details. - serial_id), + hash_code), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("policy_options", - &td.details.policy_details. - policy_options), - &no_config), + TALER_PQ_result_spec_json ("policy_json", + &td.details.policy_details. + policy_json), + &td.details.policy_details.no_policy_json), GNUNET_PQ_result_spec_timestamp ("deadline", &td.details.policy_details. deadline), - GNUNET_PQ_result_spec_uint16 ("timeout_fulfilment_state", - &td.details.policy_details. - timeout_fulfilment_state), - GNUNET_PQ_result_spec_uint16 ("fulfilment_state", + TALER_PQ_RESULT_SPEC_AMOUNT ("commitment", + &td.details.policy_details. + commitment), + TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total", + &td.details.policy_details. + 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. - fulfilment_state), + 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 }; @@ -1482,16 +1496,16 @@ 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 result the postgres result * @param num_results the number of results in @a result */ static void -lrbt_cb_table_policy_fulfilments (void *cls, - PGresult *result, - unsigned int num_results) +lrbt_cb_table_policy_fulfillments (void *cls, + PGresult *result, + unsigned int num_results) { struct LookupRecordsByTableContext *ctx = cls; struct TALER_EXCHANGEDB_TableData td = { @@ -1503,17 +1517,17 @@ lrbt_cb_table_policy_fulfilments (void *cls, bool no_config = false; bool no_timestamp = false; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_uint64 ("fulfilment_id", + GNUNET_PQ_result_spec_uint64 ("fulfillment_id", &td.serial), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_timestamp ("fulfilment_timestamp", - &td.details.policy_fulfilments. - fulfilment_timestamp), + GNUNET_PQ_result_spec_timestamp ("fulfillment_timestamp", + &td.details.policy_fulfillments. + fulfillment_timestamp), &no_timestamp), GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_string ("fulfilment_proof", - &td.details.policy_fulfilments. - fulfilment_proof), + GNUNET_PQ_result_spec_string ("fulfillment_proof", + &td.details.policy_fulfillments. + fulfillment_proof), &no_config), GNUNET_PQ_result_spec_end }; @@ -1535,52 +1549,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. * * @param cls closure diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 415417cb..062133e6 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), - GNUNET_PQ_make_prepare ( - "insert_into_table_policy_fulfilments", - "INSERT INTO policy_fulfilments" - "(fulfilment_id" - ",fulfilment_timestamp" - ",fulfilment_proof" + "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_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 aeed5de0..85accfc2 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 6acf0136..e0ad23b2 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)); |