anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

commit cd9ef9b8345e9485d6e40bad2785c7691f4d2ea0
parent 22089cd6a77323e95733da90d6e515e26a61cba1
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 29 Jul 2026 18:45:22 +0200

make the recovery document payment insert atomic via a stored procedure

Diffstat:
Mmeson.build | 2+-
Asrc/include/anastasis/anastasis-database/do_insert_recdoc_payment.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/include/anastasis/anastasis-database/insert_recdoc_payment.h | 47-----------------------------------------------
Msrc/include/anastasis_database_lib.h | 2+-
Msrc/include/meson.build | 2+-
Asrc/stasis/do_insert_recdoc_payment.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stasis/do_insert_recdoc_payment.sql | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/stasis/insert_recdoc_payment.c | 155-------------------------------------------------------------------------------
Msrc/stasis/meson.build | 3++-
Msrc/stasis/test_anastasis_db.c | 2+-
10 files changed, 175 insertions(+), 207 deletions(-)

diff --git a/meson.build b/meson.build @@ -313,7 +313,7 @@ if not get_option('only-doc') ['libanastasisutil', '1:0:0'], ['libanastasisauthorization', '0:0:0'], ['libanastasiseufin', '0:0:0'], - ['libanastasisdb', '3:0:0'], + ['libanastasisdb', '4:0:0'], ['libanastasisrest', '0:0:0'], ['libanastasistesting', '0:0:0'], ['libanastasisredux', '0:0:0'], diff --git a/src/include/anastasis/anastasis-database/do_insert_recdoc_payment.h b/src/include/anastasis/anastasis-database/do_insert_recdoc_payment.h @@ -0,0 +1,47 @@ +/* + This file is part of Anastasis + Copyright (C) 2020-2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/anastasis/anastasis-database/do_insert_recdoc_payment.h + * @brief Anastasis database: insert recdoc payment + * @author Christian Grothoff + */ +#ifndef ANASTASIS_DATABASE_DO_INSERT_RECDOC_PAYMENT_H +#define ANASTASIS_DATABASE_DO_INSERT_RECDOC_PAYMENT_H + +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_db_lib.h> +#include "anastasis_service.h" +#include "anastasis/anastasis-database/common.h" + +/** + * Store payment. Used to begin a payment, not indicative + * that the payment actually was made. (That is done + * when we increment the account's lifetime.) + * + * @param account_pub anastasis's public key + * @param post_counter how many uploads does @a amount pay for + * @param payment_secret payment secret which the user must provide with every upload + * @param amount how much we asked for + * @return transaction status + */ +enum GNUNET_DB_QueryStatus +ANASTASIS_DB_do_insert_recdoc_payment ( + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, + uint32_t post_counter, + const struct ANASTASIS_PaymentSecretP *payment_secret, + const struct TALER_Amount *amount); + +#endif diff --git a/src/include/anastasis/anastasis-database/insert_recdoc_payment.h b/src/include/anastasis/anastasis-database/insert_recdoc_payment.h @@ -1,47 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020-2022 Anastasis SARL - - Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - Anastasis 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file include/anastasis/anastasis-database/insert_recdoc_payment.h - * @brief Anastasis database: insert recdoc payment - * @author Christian Grothoff - */ -#ifndef ANASTASIS_DATABASE_INSERT_RECDOC_PAYMENT_H -#define ANASTASIS_DATABASE_INSERT_RECDOC_PAYMENT_H - -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_db_lib.h> -#include "anastasis_service.h" -#include "anastasis/anastasis-database/common.h" - -/** - * Store payment. Used to begin a payment, not indicative - * that the payment actually was made. (That is done - * when we increment the account's lifetime.) - * - * @param account_pub anastasis's public key - * @param post_counter how many uploads does @a amount pay for - * @param payment_secret payment secret which the user must provide with every upload - * @param amount how much we asked for - * @return transaction status - */ -enum GNUNET_DB_QueryStatus -ANASTASIS_DB_insert_recdoc_payment ( - const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, - uint32_t post_counter, - const struct ANASTASIS_PaymentSecretP *payment_secret, - const struct TALER_Amount *amount); - -#endif diff --git a/src/include/anastasis_database_lib.h b/src/include/anastasis_database_lib.h @@ -43,7 +43,7 @@ #include "anastasis/anastasis-database/get_recdoc_payment.h" #include "anastasis/anastasis-database/get_challenge_payment.h" #include "anastasis/anastasis-database/do_update_account_lifetime.h" -#include "anastasis/anastasis-database/insert_recdoc_payment.h" +#include "anastasis/anastasis-database/do_insert_recdoc_payment.h" #include "anastasis/anastasis-database/insert_truth_payment.h" #include "anastasis/anastasis-database/get_truth_payment.h" #include "anastasis/anastasis-database/do_verify_challenge_code.h" diff --git a/src/include/meson.build b/src/include/meson.build @@ -37,7 +37,7 @@ install_data( 'anastasis/anastasis-database/get_recdoc_payment.h', 'anastasis/anastasis-database/get_challenge_payment.h', 'anastasis/anastasis-database/do_update_account_lifetime.h', - 'anastasis/anastasis-database/insert_recdoc_payment.h', + 'anastasis/anastasis-database/do_insert_recdoc_payment.h', 'anastasis/anastasis-database/insert_truth_payment.h', 'anastasis/anastasis-database/get_truth_payment.h', 'anastasis/anastasis-database/do_verify_challenge_code.h', diff --git a/src/stasis/do_insert_recdoc_payment.c b/src/stasis/do_insert_recdoc_payment.c @@ -0,0 +1,63 @@ +/* + This file is part of Anastasis + Copyright (C) 2020-2026 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file stasis/do_insert_recdoc_payment.c + * @brief Anastasis database: record the start of a recovery document payment + * @author Christian Grothoff + */ +#include "platform.h" +#include "anastasis-db_pg.h" +#include "anastasis/anastasis-database/do_insert_recdoc_payment.h" +#include "anastasis/anastasis-database/preflight.h" +#include <taler/taler_pq_lib.h> + + +enum GNUNET_DB_QueryStatus +ANASTASIS_DB_do_insert_recdoc_payment ( + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, + uint32_t post_counter, + const struct ANASTASIS_PaymentSecretP *payment_secret, + const struct TALER_Amount *amount) +{ + struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); + struct GNUNET_TIME_Timestamp transient_expiration + = GNUNET_TIME_relative_to_timestamp (TRANSIENT_LIFETIME); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (account_pub), + GNUNET_PQ_query_param_uint32 (&post_counter), + TALER_PQ_query_param_amount (pg->conn, + amount), + GNUNET_PQ_query_param_auto_from_type (payment_secret), + GNUNET_PQ_query_param_timestamp (&now), + GNUNET_PQ_query_param_timestamp (&transient_expiration), + GNUNET_PQ_query_param_end + }; + + GNUNET_break (GNUNET_OK == + ANASTASIS_DB_preflight ()); + /* Creating the account and recording the payment happen in one statement: + they must not be separated, since the payment row references the account + and two concurrent first uploads would otherwise collide. */ + PREPARE ("do_insert_recdoc_payment", + "SELECT anastasis_do_insert_recdoc_payment" + " ($1, $2, $3, $4, $5, $6);"); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "do_insert_recdoc_payment", + params); +} + + +/* end of do_insert_recdoc_payment.c */ diff --git a/src/stasis/do_insert_recdoc_payment.sql b/src/stasis/do_insert_recdoc_payment.sql @@ -0,0 +1,59 @@ +-- +-- This file is part of Anastasis +-- Copyright (C) 2026 Anastasis SARL +-- +-- ANASTASIS 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. +-- +-- ANASTASIS 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 +-- ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +-- + +DROP FUNCTION IF EXISTS anastasis_do_insert_recdoc_payment; +CREATE FUNCTION anastasis_do_insert_recdoc_payment ( + IN in_user_id BYTEA, + IN in_post_counter INT4, + IN in_amount taler_amount, + IN in_payment_identifier BYTEA, + IN in_creation_date INT8, + IN in_transient_expiration INT8) -- lifetime given to an account created here +RETURNS void +LANGUAGE plpgsql +AS $$ +BEGIN + +-- anastasis_recdoc_payment references anastasis_user, so the account has to +-- exist first. ON CONFLICT DO NOTHING settles the race between two first +-- uploads for the same account inside the statement; the SELECT ... FOR +-- UPDATE this replaces held no lock at all, because it ran in its own +-- autocommit transaction and released the row before the inserts. +INSERT INTO anastasis_user + (user_id + ,expiration_date) + VALUES + (in_user_id + ,in_transient_expiration) +ON CONFLICT (user_id) DO NOTHING; + +INSERT INTO anastasis_recdoc_payment + (user_id + ,post_counter + ,amount + ,payment_identifier + ,creation_date) + VALUES + (in_user_id + ,in_post_counter + ,in_amount + ,in_payment_identifier + ,in_creation_date); + +END $$; + +COMMENT ON FUNCTION anastasis_do_insert_recdoc_payment + IS 'Records the start of a payment for a recovery document upload, creating the account with a transient lifetime if it does not exist yet. Atomic, so concurrent first uploads for one account cannot collide on the primary key.'; diff --git a/src/stasis/insert_recdoc_payment.c b/src/stasis/insert_recdoc_payment.c @@ -1,155 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2020-2022 Anastasis SARL - - Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - Anastasis 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file stasis/insert_recdoc_payment.c - * @brief Anastasis database: insert recdoc payment - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis-db_pg.h" -#include "anastasis/anastasis-database/insert_recdoc_payment.h" -#include "anastasis/anastasis-database/transaction.h" -#include "anastasis/anastasis-database/preflight.h" -#include <taler/taler_pq_lib.h> - - -/** - * Store payment. Used to begin a payment, not indicative - * that the payment actually was made. (That is done - * when we increment the account's lifetime.) - * - * @param account_pub anastasis's public key - * @param post_counter how many uploads does @a amount pay for - * @param payment_secret payment secret which the user must provide with every upload - * @param amount how much we asked for - * @return transaction status - */ -enum GNUNET_DB_QueryStatus -ANASTASIS_DB_insert_recdoc_payment ( - const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, - uint32_t post_counter, - const struct ANASTASIS_PaymentSecretP *payment_secret, - const struct TALER_Amount *amount) -{ - struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); - struct GNUNET_TIME_Timestamp expiration; - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_uint32 (&post_counter), - TALER_PQ_query_param_amount (pg->conn, - amount), - GNUNET_PQ_query_param_auto_from_type (payment_secret), - GNUNET_PQ_query_param_timestamp (&now), - GNUNET_PQ_query_param_end - }; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_break (GNUNET_OK == - ANASTASIS_DB_preflight ()); - PREPARE ("insert_recdoc_payment_select_account", - "SELECT" - " expiration_date " - "FROM anastasis_user" - " WHERE user_id=$1" - " FOR UPDATE;"); - PREPARE ("insert_recdoc_payment_insert_account", - "INSERT INTO anastasis_user " - "(user_id" - ",expiration_date" - ") VALUES " - "($1, $2);"); - PREPARE ("insert_recdoc_payment_insert", - "INSERT INTO anastasis_recdoc_payment " - "(user_id" - ",post_counter" - ",amount" - ",payment_identifier" - ",creation_date" - ") VALUES " - "($1, $2, $3, $4, $5);"); - - /* because of constraint at user_id, first we have to verify - if user exists, and if not, create one */ - { - struct GNUNET_PQ_QueryParam iparams[] = { - GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_end - }; - struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_timestamp ("expiration_date", - &expiration), - GNUNET_PQ_result_spec_end - }; - - qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "insert_recdoc_payment_select_account", - iparams, - rs); - } - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - return qs; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - { - /* create new user with short lifetime */ - struct GNUNET_TIME_Timestamp exp - = GNUNET_TIME_relative_to_timestamp (TRANSIENT_LIFETIME); - struct GNUNET_PQ_QueryParam iparams[] = { - GNUNET_PQ_query_param_auto_from_type (account_pub), - GNUNET_PQ_query_param_timestamp (&exp), - GNUNET_PQ_query_param_end - }; - - qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_recdoc_payment_insert_account", - iparams); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - return GNUNET_DB_STATUS_HARD_ERROR; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - GNUNET_break (0); - return GNUNET_DB_STATUS_HARD_ERROR; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* successful, continue below */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Created new account %s with transient life until %s\n", - TALER_B2S (account_pub), - GNUNET_TIME_timestamp2s (exp)); - break; - } - } - /* continue below */ - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* handle case below */ - break; - } - - return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "insert_recdoc_payment_insert", - params); -} - - -/* end of insert_recdoc_payment.c */ diff --git a/src/stasis/meson.build b/src/stasis/meson.build @@ -17,6 +17,7 @@ install_data( procedures_sql = [ 'procedures-prelude.sql', 'do_gc.sql', + 'do_insert_recdoc_payment.sql', 'do_update_account_lifetime.sql', 'commit.sql', ] @@ -57,7 +58,7 @@ libanastasisdb_SOURCES = [ 'insert_auth_iban_in.c', 'insert_challenge_payment.c', 'update_to_challenge_payment_refunded.c', - 'insert_recdoc_payment.c', + 'do_insert_recdoc_payment.c', 'insert_truth_payment.c', 'do_insert_recovery_document.c', 'insert_truth.c', diff --git a/src/stasis/test_anastasis_db.c b/src/stasis/test_anastasis_db.c @@ -140,7 +140,7 @@ run (void *cls) memset (&accountPubP, 2, sizeof (accountPubP)); memset (&accountSig, 3, sizeof (accountSig)); FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - ANASTASIS_DB_insert_recdoc_payment ( + ANASTASIS_DB_do_insert_recdoc_payment ( &accountPubP, post_counter, &paymentSecretP,