[WiP] added TALER_AMOUNT type to Postgres - first in age_withdraw

- Added a type TALER_AMOUNT (val INT8, frac INT4) to Postgres.

- Added PLSQL functions/procedures
	- amount_normalize(a)
	- amount_add(a, b)
	- amount_left_minus_right(l, r, diff, ok bool)

- Added PQ-helper functions
	- TALER_PQ_query_param_amount_tuple()
	- TALER_PQ_result_spec_amount_tuple()

- In table 'age_withdraw', changed fields 'amount_with_fee_val' and '..._frac'
  into single field 'amount_with_fee' be of type TALER_AMOUNT

- Changed functions/stored procedures 'do_age_withdraw' and
  'get_age_withdraw' to use new APIs.

=> make check runs through without errors,
   age-withdraw and -reveal test passes.
This commit is contained in:
Özgür Kesim 2023-07-27 23:57:07 +02:00
parent 722e00b1e9
commit 571d43cef3
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
9 changed files with 377 additions and 48 deletions

View File

@ -29,8 +29,7 @@ BEGIN
'(age_withdraw_id BIGINT GENERATED BY DEFAULT AS IDENTITY' '(age_withdraw_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
',h_commitment BYTEA NOT NULL CONSTRAINT h_commitment_length CHECK(LENGTH(h_commitment)=64)' ',h_commitment BYTEA NOT NULL CONSTRAINT h_commitment_length CHECK(LENGTH(h_commitment)=64)'
',max_age SMALLINT NOT NULL CONSTRAINT max_age_positive CHECK(max_age>=0)' ',max_age SMALLINT NOT NULL CONSTRAINT max_age_positive CHECK(max_age>=0)'
',amount_with_fee_val INT8 NOT NULL' ',amount_with_fee TALER_AMOUNT NOT NULL'
',amount_with_fee_frac INT4 NOT NULL'
',reserve_pub BYTEA NOT NULL CONSTRAINT reserve_pub_length CHECK(LENGTH(reserve_pub)=32)' ',reserve_pub BYTEA NOT NULL CONSTRAINT reserve_pub_length CHECK(LENGTH(reserve_pub)=32)'
',reserve_sig BYTEA NOT NULL CONSTRAINT reserve_sig_length CHECK(LENGTH(reserve_sig)=64)' ',reserve_sig BYTEA NOT NULL CONSTRAINT reserve_sig_length CHECK(LENGTH(reserve_sig)=64)'
',noreveal_index SMALLINT NOT NULL CONSTRAINT noreveal_index_positive CHECK(noreveal_index>=0)' ',noreveal_index SMALLINT NOT NULL CONSTRAINT noreveal_index_positive CHECK(noreveal_index>=0)'

View File

@ -137,6 +137,97 @@ COMMENT ON FUNCTION comment_partitioned_column
IS 'Generic function to create a comment on column of a table that is partitioned.'; IS 'Generic function to create a comment on column of a table that is partitioned.';
--------------------------------------------------------------
-- Taler amounts and helper functiosn
-------------------------------------------------------------
DO $$
BEGIN
CREATE TYPE TALER_AMOUNT
AS (val INT8
,frac INT4);
COMMENT ON TYPE TALER_AMOUNT
IS 'Type to store a TALER-amount as (val, frac) pair.';
EXCEPTION
WHEN duplicate_object THEN null;
END
$$;
CREATE PROCEDURE amount_normalize(
IN amount TALER_AMOUNT
,OUT normalized TALER_AMOUNT
)
LANGUAGE plpgsql
AS $$
BEGIN
normalized.val = amount.val + amount.frac / 100000000;
normalized.frac = amount.frac % 100000000;
END $$;
COMMENT ON PROCEDURE amount_normalize
IS 'Returns the normalized amount by adding to the .val the value of (.frac / 100000000) and removing the modulus 100000000 from .frac.';
CREATE PROCEDURE amount_add(
IN a TALER_AMOUNT
,IN b TALER_AMOUNT
,OUT sum TALER_AMOUNT
)
LANGUAGE plpgsql
AS $$
BEGIN
sum = (a.val + b.val, a.frac + b.frac);
CALL amount_normalize(sum ,sum);
IF (sum.val > (1<<52))
THEN
RAISE EXCEPTION 'addition overflow';
END IF;
END $$;
COMMENT ON PROCEDURE amount_add
IS 'Returns the normalized sum of two amounts. It raises an exception when the resulting .val is larger than 2^52';
CREATE FUNCTION amount_left_minus_right(
IN l TALER_AMOUNT
,IN r TALER_AMOUNT
,OUT diff TALER_AMOUNT
,OUT ok BOOLEAN
)
LANGUAGE plpgsql
AS $$
BEGIN
IF (l.val > r.val)
THEN
ok = TRUE;
IF (l.frac >= r.frac)
THEN
diff.val = l.val - r.val;
diff.frac = l.frac - r.frac;
ELSE
diff.val = l.val - r.val - 1;
diff.frac = l.frac + 100000000 - r.frac;
END IF;
ELSE
IF (l.val = r.val) AND (l.frac >= r.frac)
THEN
diff.val = 0;
diff.frac = l.frac - r.frac;
ok = TRUE;
ELSE
diff = (-1, -1);
ok = FALSE;
END IF;
END IF;
RETURN;
END $$;
COMMENT ON FUNCTION amount_left_minus_right
IS 'Subtracts the right amount from the left and returns the difference and TRUE, if the left amount is larger than the right, or an invalid amount and FALSE otherwise.';
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Main DB setup loop -- Main DB setup loop
--------------------------------------------------------------------------- ---------------------------------------------------------------------------

View File

@ -16,8 +16,7 @@
-- @author Özgür Kesim -- @author Özgür Kesim
CREATE OR REPLACE FUNCTION exchange_do_age_withdraw( CREATE OR REPLACE FUNCTION exchange_do_age_withdraw(
IN amount_val INT8, IN amount_with_fee TALER_AMOUNT,
IN amount_frac INT4,
IN rpub BYTEA, IN rpub BYTEA,
IN rsig BYTEA, IN rsig BYTEA,
IN now INT8, IN now INT8,
@ -38,8 +37,9 @@ LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
reserve_gc INT8; reserve_gc INT8;
reserve_val INT8; difference RECORD;
reserve_frac INT4; balance TALER_AMOUNT;
new_balance TALER_AMOUNT;
not_before date; not_before date;
earliest_date date; earliest_date date;
BEGIN BEGIN
@ -55,8 +55,8 @@ SELECT
,gc_date ,gc_date
,birthday ,birthday
INTO INTO
reserve_val balance.val
,reserve_frac ,balance.frac
,reserve_gc ,reserve_gc
,reserve_birthday ,reserve_birthday
FROM exchange.reserves FROM exchange.reserves
@ -102,31 +102,23 @@ END IF;
age_ok = TRUE; age_ok = TRUE;
required_age=0; required_age=0;
-- Check reserve balance is sufficient. -- Check reserve balance is sufficient.
IF (reserve_val > amount_val) SELECT *
THEN INTO
IF (reserve_frac >= amount_frac) difference
THEN FROM
reserve_val=reserve_val - amount_val; amount_left_minus_right(
reserve_frac=reserve_frac - amount_frac; balance
ELSE ,amount_with_fee);
reserve_val=reserve_val - amount_val - 1;
reserve_frac=reserve_frac + 100000000 - amount_frac; balance_ok = difference.ok;
END IF;
ELSE IF NOT balance_ok
IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac) THEN
THEN RETURN;
reserve_val=0;
reserve_frac=reserve_frac - amount_frac;
ELSE
balance_ok=FALSE;
RETURN;
END IF;
END IF; END IF;
balance_ok=TRUE; new_balance = difference.diff;
-- Calculate new expiration dates. -- Calculate new expiration dates.
min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc); min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc);
@ -134,8 +126,8 @@ min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc);
-- Update reserve balance. -- Update reserve balance.
UPDATE reserves SET UPDATE reserves SET
gc_date=min_reserve_gc gc_date=min_reserve_gc
,current_balance_val=reserve_val ,current_balance_val=new_balance.val
,current_balance_frac=reserve_frac ,current_balance_frac=new_balance.frac
WHERE WHERE
reserves.reserve_pub=rpub; reserves.reserve_pub=rpub;
@ -143,8 +135,7 @@ WHERE
INSERT INTO exchange.age_withdraw INSERT INTO exchange.age_withdraw
(h_commitment (h_commitment
,max_age ,max_age
,amount_with_fee_val ,amount_with_fee
,amount_with_fee_frac
,reserve_pub ,reserve_pub
,reserve_sig ,reserve_sig
,noreveal_index ,noreveal_index
@ -154,8 +145,7 @@ INSERT INTO exchange.age_withdraw
VALUES VALUES
(h_commitment (h_commitment
,maximum_age_committed ,maximum_age_committed
,amount_val ,amount_with_fee
,amount_frac
,rpub ,rpub
,rsig ,rsig
,noreveal_index ,noreveal_index
@ -176,6 +166,6 @@ END IF;
END $$; END $$;
COMMENT ON FUNCTION exchange_do_age_withdraw(INT8, INT4, BYTEA, BYTEA, INT8, INT8, BYTEA, INT2, INT2, BYTEA[], INT8[], BYTEA[]) COMMENT ON FUNCTION exchange_do_age_withdraw(TALER_AMOUNT, BYTEA, BYTEA, INT8, INT8, BYTEA, INT2, INT2, BYTEA[], INT8[], BYTEA[])
IS 'Checks whether the reserve has sufficient balance for an age-withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the blinded planchets and denomination signatures, or signaling conflict'; IS 'Checks whether the reserve has sufficient balance for an age-withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the blinded planchets and denomination signatures, or signaling conflict';

View File

@ -44,7 +44,8 @@ TEH_PG_do_age_withdraw (
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Timestamp gc; struct GNUNET_TIME_Timestamp gc;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
TALER_PQ_query_param_amount (&commitment->amount_with_fee), TALER_PQ_query_param_amount_tuple (pg->conn,
&commitment->amount_with_fee),
GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_pub), GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_pub),
GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_sig), GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_sig),
GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_timestamp (&now),
@ -96,7 +97,7 @@ TEH_PG_do_age_withdraw (
",reserve_birthday" ",reserve_birthday"
",conflict" ",conflict"
" FROM exchange_do_age_withdraw" " FROM exchange_do_age_withdraw"
" ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12);"); " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_age_withdraw", "call_age_withdraw",
params, params,

View File

@ -48,8 +48,9 @@ TEH_PG_get_age_withdraw (
&aw->reserve_pub), &aw->reserve_pub),
GNUNET_PQ_result_spec_uint16 ("max_age", GNUNET_PQ_result_spec_uint16 ("max_age",
&aw->max_age), &aw->max_age),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", TALER_PQ_result_spec_amount_tuple ("amount_with_fee",
&aw->amount_with_fee), pg->currency,
&aw->amount_with_fee),
GNUNET_PQ_result_spec_uint16 ("noreveal_index", GNUNET_PQ_result_spec_uint16 ("noreveal_index",
&aw->noreveal_index), &aw->noreveal_index),
TALER_PQ_result_spec_array_blinded_coin_hash ( TALER_PQ_result_spec_array_blinded_coin_hash (
@ -83,8 +84,7 @@ TEH_PG_get_age_withdraw (
",reserve_sig" ",reserve_sig"
",reserve_pub" ",reserve_pub"
",max_age" ",max_age"
",amount_with_fee_val" ",amount_with_fee"
",amount_with_fee_frac"
",noreveal_index" ",noreveal_index"
",h_blind_evs" ",h_blind_evs"
",denom_sigs" ",denom_sigs"

View File

@ -1421,6 +1421,8 @@ run (void *cls)
bool found; bool found;
bool nonce_ok; bool nonce_ok;
bool balance_ok; bool balance_ok;
bool age_ok;
uint16_t maximum_age;
uint64_t ruuid; uint64_t ruuid;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
@ -1428,9 +1430,12 @@ run (void *cls)
NULL, NULL,
&cbc, &cbc,
now, now,
false,
&found, &found,
&balance_ok, &balance_ok,
&nonce_ok, &nonce_ok,
&age_ok,
&maximum_age,
&ruuid)); &ruuid));
GNUNET_assert (found); GNUNET_assert (found);
GNUNET_assert (nonce_ok); GNUNET_assert (nonce_ok);

View File

@ -51,6 +51,19 @@ TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x);
struct GNUNET_PQ_QueryParam struct GNUNET_PQ_QueryParam
TALER_PQ_query_param_amount (const struct TALER_Amount *x); TALER_PQ_query_param_amount (const struct TALER_Amount *x);
/**
* Generate query parameter (as record tuple) for an amount, consisting
* of the two components "value" and "fraction" in this order. The
* types must be a 64-bit integer and a 32-bit integer
* respectively. The currency is dropped.
*
* @param db The database context for OID lookup
* @param amount pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
TALER_PQ_query_param_amount_tuple (
const struct GNUNET_PQ_Context *db,
const struct TALER_Amount *amount);
/** /**
* Generate query parameter for a denomination public * Generate query parameter for a denomination public
@ -180,6 +193,18 @@ TALER_PQ_result_spec_amount (const char *name,
const char *currency, const char *currency,
struct TALER_Amount *amount); struct TALER_Amount *amount);
/**
* Currency amount expected, from a record-field of (DB) taler_amount type
*
* @param name name of the field in the table
* @param currency currency to use for @a amount
* @param[out] amount where to store the result
* @return array entry for the result specification to use
*/
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_amount_tuple (const char *name,
const char *currency,
struct TALER_Amount *amount);
/** /**
* Denomination public key expected. * Denomination public key expected.

View File

@ -21,6 +21,7 @@
* @author Christian Grothoff * @author Christian Grothoff
*/ */
#include "platform.h" #include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_pq_lib.h> #include <gnunet/gnunet_pq_lib.h>
#include "taler_pq_lib.h" #include "taler_pq_lib.h"
@ -149,6 +150,84 @@ TALER_PQ_query_param_amount (const struct TALER_Amount *x)
} }
/**
* Function called to convert input amount into SQL parameter as tuple.
*
* @param cls closure
* @param data pointer to input argument, here a `struct TALER_Amount`
* @param data_len number of bytes in @a data (if applicable)
* @param[out] param_values SQL data to set
* @param[out] param_lengths SQL length data to set
* @param[out] param_formats SQL format data to set
* @param param_length number of entries available in the @a param_values, @a param_lengths and @a param_formats arrays
* @param[out] scratch buffer for dynamic allocations (to be done via GNUNET_malloc()
* @param scratch_length number of entries left in @a scratch
* @return -1 on error, number of offsets used in @a scratch otherwise
*/
static int
qconv_amount_tuple (void *cls,
const void *data,
size_t data_len,
void *param_values[],
int param_lengths[],
int param_formats[],
unsigned int param_length,
void *scratch[],
unsigned int scratch_length)
{
const struct GNUNET_PQ_Context *db = cls;
const struct TALER_Amount *amount = data;
size_t sz;
GNUNET_assert (NULL != db);
GNUNET_assert (NULL != amount);
GNUNET_assert (1 == param_length);
GNUNET_assert (1 <= scratch_length);
GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
{
char *out;
struct TALER_PQ_Amount_P d = MAKE_TALER_PQ_AMOUNT_P (db, amount);
sz = sizeof(uint32_t); /* number of elements in tuple */
sz += sizeof(d);
out = GNUNET_malloc (sz);
scratch[0] = out;
*(uint32_t *) out = htonl (2);
out += sizeof(uint32_t);
*(struct TALER_PQ_Amount_P*) out = d;
}
param_values[0] = scratch[0];
param_lengths[0] = sz;
param_formats[0] = 1;
return 1;
}
struct GNUNET_PQ_QueryParam
TALER_PQ_query_param_amount_tuple (
const struct GNUNET_PQ_Context *db,
const struct TALER_Amount *amount)
{
struct GNUNET_PQ_QueryParam res = {
.conv_cls = (void *) db,
.conv = &qconv_amount_tuple,
.data = amount,
.size = sizeof (*amount),
.num_params = 1,
};
return res;
}
/** /**
* Function called to convert input argument into SQL parameters. * Function called to convert input argument into SQL parameters.
* *
@ -793,7 +872,7 @@ qconv_array (
RETURN_UNLESS ((0 == num) || (y / num == x)); RETURN_UNLESS ((0 == num) || (y / num == x));
/* size of header */ /* size of header */
total_size = x = sizeof(struct TALER_PQ_ArrayHeader); total_size = x = sizeof(struct TALER_PQ_ArrayHeader_P);
total_size += y; total_size += y;
RETURN_UNLESS (total_size >= x); RETURN_UNLESS (total_size >= x);
@ -862,7 +941,7 @@ qconv_array (
/* Write data */ /* Write data */
{ {
char *out = elements; char *out = elements;
struct TALER_PQ_ArrayHeader h = { struct TALER_PQ_ArrayHeader_P h = {
.ndim = htonl (1), /* We only support one-dimensional arrays */ .ndim = htonl (1), /* We only support one-dimensional arrays */
.has_null = htonl (0), /* We do not support NULL entries in arrays */ .has_null = htonl (0), /* We do not support NULL entries in arrays */
.lbound = htonl (1), /* Default start index value */ .lbound = htonl (1), /* Default start index value */

View File

@ -264,6 +264,145 @@ TALER_PQ_result_spec_amount (const char *name,
} }
/**
* Extract an amount from a tuple from a Postgres database @a result at row @a row.
*
* @param cls closure, a `const char *` giving the currency
* @param result where to extract data from
* @param row row to extract data from
* @param fname name (or prefix) of the fields to extract from
* @param[in,out] dst_size where to store size of result, may be NULL
* @param[out] dst where to store the result
* @return
* #GNUNET_YES if all results could be extracted
* #GNUNET_NO if at least one result was NULL
* #GNUNET_SYSERR if a result was invalid (non-existing field)
*/
static enum GNUNET_GenericReturnValue
extract_amount_tuple (void *cls,
PGresult *result,
int row,
const char *fname,
size_t *dst_size,
void *dst)
{
struct TALER_Amount *r_amount = dst;
const char *currency = cls;
int col;
int len;
if (sizeof (struct TALER_Amount) != *dst_size)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
/* Set return value to invalid in case we don't finish */
memset (r_amount,
0,
sizeof (struct TALER_Amount));
col = PQfnumber (result,
fname);
if (col < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Field `%s' does not exist in result\n",
fname);
return GNUNET_SYSERR;
}
if (PQgetisnull (result,
row,
col))
{
return GNUNET_NO;
}
/* Parse the tuple */
{
char *in;
uint32_t num;
struct TALER_PQ_Amount_P ap;
int size;
const static int expected_size = sizeof(uint32_t) /* length */
+ sizeof(struct TALER_PQ_Amount_P);
size = PQgetlength (result,
row,
col);
if (expected_size != size)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Incorrect size of binary field `%s' (got %d, expected %d)\n",
fname,
size,
expected_size);
return GNUNET_SYSERR;
}
in = PQgetvalue (result,
row,
col);
num = ntohl (*(uint32_t *) in);
if (2 != num)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Incorrect number of elements in tuple-field `%s'\n",
fname);
return GNUNET_SYSERR;
}
in += sizeof(uint32_t);
ap = *(struct TALER_PQ_Amount_P *) in;
/* TODO[oec]: OID-checks? */
r_amount->value = GNUNET_ntohll (ap.v);
r_amount->fraction = ntohl (ap.f);
}
if (r_amount->value >= TALER_AMOUNT_MAX_VALUE)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Value in field `%s' exceeds legal range\n",
fname);
return GNUNET_SYSERR;
}
if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fraction in field `%s' exceeds legal range\n",
fname);
return GNUNET_SYSERR;
}
len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
strlen (currency));
GNUNET_memcpy (r_amount->currency,
currency,
len);
return GNUNET_OK;
}
struct GNUNET_PQ_ResultSpec
TALER_PQ_result_spec_amount_tuple (const char *name,
const char *currency,
struct TALER_Amount *amount)
{
struct GNUNET_PQ_ResultSpec res = {
.conv = &extract_amount_tuple,
.cls = (void *) currency,
.dst = (void *) amount,
.dst_size = sizeof (*amount),
.fname = name
};
return res;
}
/** /**
* Extract data from a Postgres database @a result at row @a row. * Extract data from a Postgres database @a result at row @a row.
* *
@ -1027,7 +1166,7 @@ extract_array_generic (
int data_sz; int data_sz;
char *data; char *data;
void *out = NULL; void *out = NULL;
struct TALER_PQ_ArrayHeader header; struct TALER_PQ_ArrayHeader_P header;
int col_num; int col_num;
GNUNET_assert (NULL != dst); GNUNET_assert (NULL != dst);
@ -1053,8 +1192,8 @@ extract_array_generic (
FAIL_IF (NULL == data); FAIL_IF (NULL == data);
{ {
struct TALER_PQ_ArrayHeader *h = struct TALER_PQ_ArrayHeader_P *h =
(struct TALER_PQ_ArrayHeader *) data; (struct TALER_PQ_ArrayHeader_P *) data;
header.ndim = ntohl (h->ndim); header.ndim = ntohl (h->ndim);
header.has_null = ntohl (h->has_null); header.has_null = ntohl (h->has_null);