aboutsummaryrefslogtreecommitdiff
path: root/src/exchangedb
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2022-10-14 18:56:59 +0200
committerÖzgür Kesim <oec-taler@kesim.org>2022-10-14 18:56:59 +0200
commit054e157af8c768062dd0a8e66614da18407fca28 (patch)
treefcd792c6ed7f4384e9b29f8e200f62837cd00f1e /src/exchangedb
parent956e3c3065ddb762dbe01fd720cc5ef403232564 (diff)
WIP: policy_details handling continued
- policy details generated on deposit/batch-deposit requests - insert or update of policy details in the DB - accumulation of amounts of multiple deposits for the same policy_details
Diffstat (limited to 'src/exchangedb')
-rw-r--r--src/exchangedb/exchange-0001-part.sql111
-rw-r--r--src/exchangedb/irbt_callbacks.c16
-rw-r--r--src/exchangedb/lrbt_callbacks.c106
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c302
-rw-r--r--src/exchangedb/procedures.sql152
-rw-r--r--src/exchangedb/test_exchangedb.c1
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));